@appxdigital/appx-core 0.1.122 → 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.
- package/CHANGELOG.md +40 -1
- package/README.md +24 -2
- package/dist/appx-core.module.d.ts +1 -0
- package/dist/appx-core.module.d.ts.map +1 -1
- package/dist/appx-core.module.js +1 -0
- package/dist/common/bigint-serializer.d.ts +1 -0
- package/dist/common/bigint-serializer.d.ts.map +1 -0
- package/dist/common/bigint-serializer.js +18 -0
- package/dist/common/decorators/scalar-field.decorator.d.ts +22 -0
- package/dist/common/decorators/scalar-field.decorator.d.ts.map +1 -0
- package/dist/common/decorators/scalar-field.decorator.js +103 -0
- package/dist/config/generate-all.js +21 -54
- package/dist/config/generate-controllers.d.ts +2 -1
- package/dist/config/generate-controllers.d.ts.map +1 -1
- package/dist/config/generate-controllers.js +7 -22
- package/dist/config/generate-dtos.d.ts +14 -1
- package/dist/config/generate-dtos.d.ts.map +1 -1
- package/dist/config/generate-dtos.js +85 -28
- package/dist/config/generate-models.d.ts +3 -0
- package/dist/config/generate-models.d.ts.map +1 -0
- package/dist/config/generate-models.js +107 -0
- package/dist/config/generate-modules.d.ts +30 -1
- package/dist/config/generate-modules.d.ts.map +1 -1
- package/dist/config/generate-modules.js +145 -86
- package/dist/config/generate-resolvers.d.ts +7 -1
- package/dist/config/generate-resolvers.d.ts.map +1 -1
- package/dist/config/generate-resolvers.js +10 -17
- package/dist/config/generate-services.d.ts +2 -1
- package/dist/config/generate-services.d.ts.map +1 -1
- package/dist/config/generate-services.js +7 -16
- package/dist/config/utils.d.ts +38 -1
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +76 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/docs/README.md +1 -0
- package/docs/dtos.md +13 -1
- package/docs/generate.md +57 -0
- package/package.json +3 -1
|
@@ -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
|
-
|
|
26
|
+
exports.scaffoldResolver = 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 outputPath = path.join(process.cwd(), 'src/modules');
|
|
31
30
|
/**
|
|
32
31
|
* Generic resolver template for a given model
|
|
@@ -68,20 +67,14 @@ export class ${model}Resolver extends ${model}GenericResolver {
|
|
|
68
67
|
}
|
|
69
68
|
`;
|
|
70
69
|
/**
|
|
71
|
-
*
|
|
70
|
+
* Scaffold the (read-only) GraphQL resolver file for a single model (once;
|
|
71
|
+
* never overwritten). Imports the prisma-nestjs-graphql artifacts under
|
|
72
|
+
* `src/generated/<folder>/`, so the deploy-safe pass (`prisma generate`) must
|
|
73
|
+
* have produced them first.
|
|
72
74
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const modelName = (0, utils_1.kebabToPascalCase)(folder);
|
|
77
|
-
const modelOutputPath = path.join(outputPath, 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
|
-
const resolverPath = path.join(modelOutputPath, `${folder}.resolver.ts`);
|
|
75
|
+
function scaffoldResolver(modelName) {
|
|
76
|
+
const folder = (0, utils_1.modelFolder)(modelName);
|
|
77
|
+
const resolverPath = path.join(outputPath, folder, `${folder}.resolver.ts`);
|
|
86
78
|
(0, utils_1.createFileIfNotExists)(resolverPath, genericResolverTemplate(modelName, folder));
|
|
87
|
-
}
|
|
79
|
+
}
|
|
80
|
+
exports.scaffoldResolver = scaffoldResolver;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-services.d.ts","sourceRoot":"","sources":["../../src/config/generate-services.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"generate-services.d.ts","sourceRoot":"","sources":["../../src/config/generate-services.ts"],"names":[],"mappings":"AAwBA,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAIvD"}
|
|
@@ -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
|
-
|
|
26
|
+
exports.scaffoldService = 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 servicesOutputPath = path.join(process.cwd(), 'src/modules');
|
|
31
30
|
/**
|
|
32
31
|
* Generic service template for a given model
|
|
@@ -45,18 +44,10 @@ export class ${model}Service extends CoreService<${model}> {
|
|
|
45
44
|
// Override methods or add custom logic as needed
|
|
46
45
|
}
|
|
47
46
|
`;
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (utils_1.IGNORE_FOLDERS.includes(folder))
|
|
53
|
-
return;
|
|
54
|
-
const modelName = (0, utils_1.kebabToPascalCase)(folder);
|
|
55
|
-
const modelOutputPath = path.join(servicesOutputPath, folder);
|
|
56
|
-
if (!fs.existsSync(modelOutputPath)) {
|
|
57
|
-
fs.mkdirSync(modelOutputPath, { recursive: true });
|
|
58
|
-
console.log(`Folder for model ${modelName} created.`);
|
|
59
|
-
}
|
|
60
|
-
const servicePath = path.join(modelOutputPath, `${folder}.service.ts`);
|
|
47
|
+
/** Scaffold the service file for a single model (once; never overwritten). */
|
|
48
|
+
function scaffoldService(modelName) {
|
|
49
|
+
const folder = (0, utils_1.modelFolder)(modelName);
|
|
50
|
+
const servicePath = path.join(servicesOutputPath, folder, `${folder}.service.ts`);
|
|
61
51
|
(0, utils_1.createFileIfNotExists)(servicePath, genericServiceTemplate(modelName));
|
|
62
|
-
}
|
|
52
|
+
}
|
|
53
|
+
exports.scaffoldService = scaffoldService;
|
package/dist/config/utils.d.ts
CHANGED
|
@@ -14,5 +14,42 @@ export declare const GENERATED_BANNER: string;
|
|
|
14
14
|
* directories as needed.
|
|
15
15
|
*/
|
|
16
16
|
export declare const writeGeneratedFile: (filePath: string, content: string) => void;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Framework-owned models. Excluded from model discovery: no generic CRUD module,
|
|
19
|
+
* controller, service, resolver, or DTO is scaffolded for them. `Session` /
|
|
20
|
+
* `UserRefreshToken` are framework plumbing; `User` is owned by the auth module
|
|
21
|
+
* (generic User CRUD would be a mass-assignment surface, and `/auth/*` already
|
|
22
|
+
* covers user endpoints), so it is hidden from the module wizard entirely.
|
|
23
|
+
*/
|
|
24
|
+
export declare const FRAMEWORK_MODELS: string[];
|
|
25
|
+
/**
|
|
26
|
+
* The kebab-case folder / file basename for a model. Matches the folder naming
|
|
27
|
+
* of the prisma-nestjs-graphql generator output under `src/generated/`, so the
|
|
28
|
+
* scaffolded resolver's imports (`../../generated/<folder>/<folder>.model`)
|
|
29
|
+
* resolve. E.g. `ProjectMember` -> `project-member`, `TypeSample` -> `type-sample`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const modelFolder: (modelName: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Loads the datamodel from the consumer project's generated Prisma client and
|
|
34
|
+
* returns the discoverable (non-framework-owned) models. This is the single
|
|
35
|
+
* source of truth for "what models exist" — independent of whatever folders the
|
|
36
|
+
* GraphQL plugin happened to emit. Requires `@prisma/client` lazily so callers
|
|
37
|
+
* can run `prisma generate` first and pick up a fresh schema in the same process.
|
|
38
|
+
*/
|
|
39
|
+
export declare const loadAllModels: () => any[];
|
|
40
|
+
export declare const loadModels: () => any[];
|
|
41
|
+
/** True if a module exists at the model's canonical folder under src/modules. */
|
|
42
|
+
export declare const moduleExists: (modelName: string) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The set of model names already served by existing module source — detected by
|
|
45
|
+
* the `CoreController<Model>` / `CoreService<Model>` type argument. Pure (takes
|
|
46
|
+
* file contents) so it is unit-testable. This catches a module that serves a
|
|
47
|
+
* model under a NON-canonical folder name (e.g. a hand-written pluralised
|
|
48
|
+
* `wearable-connections/` module owning the `WearableConnection` model), which a
|
|
49
|
+
* folder-name check (`moduleExists`) would miss — preventing the wizard from
|
|
50
|
+
* scaffolding a duplicate module + controller on a second route.
|
|
51
|
+
*/
|
|
52
|
+
export declare const modelsServedBySource: (contents: string[]) => Set<string>;
|
|
53
|
+
/** Scans src/modules for the models already served by an existing module. */
|
|
54
|
+
export declare const ownedModels: () => Set<string>;
|
|
18
55
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,SAStE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAGoD,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,aAAc,MAAM,WAAW,MAAM,SAInE,CAAC;AAEF,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/config/utils.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAS,MAAM,WAK5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAAc,MAAM,WAAW,MAAM,SAStE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAGoD,CAAC;AAElF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,aAAc,MAAM,WAAW,MAAM,SAInE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,UAA0C,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,cAAe,MAAM,KAAG,MAAsC,CAAC;AAEvF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,QAAO,GAAG,EAInC,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,GAAG,EAAuE,CAAC;AAEzG,iFAAiF;AACjF,eAAO,MAAM,YAAY,cAAe,MAAM,KAAG,OAGhD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,aAAc,MAAM,EAAE,KAAG,IAAI,MAAM,CAQnE,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QAAO,IAAI,MAAM,CAYxC,CAAC"}
|
package/dist/config/utils.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
26
|
+
exports.ownedModels = exports.modelsServedBySource = exports.moduleExists = exports.loadModels = exports.loadAllModels = exports.modelFolder = exports.FRAMEWORK_MODELS = exports.writeGeneratedFile = exports.GENERATED_BANNER = exports.createFileIfNotExists = exports.pascalToKebabCase = exports.kebabToPascalCase = void 0;
|
|
27
27
|
const fs = __importStar(require("fs"));
|
|
28
28
|
const path = __importStar(require("path"));
|
|
29
29
|
const kebabToPascalCase = (str) => {
|
|
@@ -72,13 +72,78 @@ const writeGeneratedFile = (filePath, content) => {
|
|
|
72
72
|
console.log(`Generated ${filePath}`);
|
|
73
73
|
};
|
|
74
74
|
exports.writeGeneratedFile = writeGeneratedFile;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Framework-owned models. Excluded from model discovery: no generic CRUD module,
|
|
77
|
+
* controller, service, resolver, or DTO is scaffolded for them. `Session` /
|
|
78
|
+
* `UserRefreshToken` are framework plumbing; `User` is owned by the auth module
|
|
79
|
+
* (generic User CRUD would be a mass-assignment surface, and `/auth/*` already
|
|
80
|
+
* covers user endpoints), so it is hidden from the module wizard entirely.
|
|
81
|
+
*/
|
|
82
|
+
exports.FRAMEWORK_MODELS = ['Session', 'UserRefreshToken', 'User'];
|
|
83
|
+
/**
|
|
84
|
+
* The kebab-case folder / file basename for a model. Matches the folder naming
|
|
85
|
+
* of the prisma-nestjs-graphql generator output under `src/generated/`, so the
|
|
86
|
+
* scaffolded resolver's imports (`../../generated/<folder>/<folder>.model`)
|
|
87
|
+
* resolve. E.g. `ProjectMember` -> `project-member`, `TypeSample` -> `type-sample`.
|
|
88
|
+
*/
|
|
89
|
+
const modelFolder = (modelName) => (0, exports.pascalToKebabCase)(modelName);
|
|
90
|
+
exports.modelFolder = modelFolder;
|
|
91
|
+
/**
|
|
92
|
+
* Loads the datamodel from the consumer project's generated Prisma client and
|
|
93
|
+
* returns the discoverable (non-framework-owned) models. This is the single
|
|
94
|
+
* source of truth for "what models exist" — independent of whatever folders the
|
|
95
|
+
* GraphQL plugin happened to emit. Requires `@prisma/client` lazily so callers
|
|
96
|
+
* can run `prisma generate` first and pick up a fresh schema in the same process.
|
|
97
|
+
*/
|
|
98
|
+
const loadAllModels = () => {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
100
|
+
const { Prisma } = require(path.join(process.cwd(), 'node_modules', '@prisma/client'));
|
|
101
|
+
return Prisma.dmmf.datamodel.models;
|
|
102
|
+
};
|
|
103
|
+
exports.loadAllModels = loadAllModels;
|
|
104
|
+
const loadModels = () => (0, exports.loadAllModels)().filter((m) => !exports.FRAMEWORK_MODELS.includes(m.name));
|
|
105
|
+
exports.loadModels = loadModels;
|
|
106
|
+
/** True if a module exists at the model's canonical folder under src/modules. */
|
|
107
|
+
const moduleExists = (modelName) => {
|
|
108
|
+
const folder = (0, exports.modelFolder)(modelName);
|
|
109
|
+
return fs.existsSync(path.join(process.cwd(), 'src/modules', folder, `${folder}.module.ts`));
|
|
110
|
+
};
|
|
111
|
+
exports.moduleExists = moduleExists;
|
|
112
|
+
/**
|
|
113
|
+
* The set of model names already served by existing module source — detected by
|
|
114
|
+
* the `CoreController<Model>` / `CoreService<Model>` type argument. Pure (takes
|
|
115
|
+
* file contents) so it is unit-testable. This catches a module that serves a
|
|
116
|
+
* model under a NON-canonical folder name (e.g. a hand-written pluralised
|
|
117
|
+
* `wearable-connections/` module owning the `WearableConnection` model), which a
|
|
118
|
+
* folder-name check (`moduleExists`) would miss — preventing the wizard from
|
|
119
|
+
* scaffolding a duplicate module + controller on a second route.
|
|
120
|
+
*/
|
|
121
|
+
const modelsServedBySource = (contents) => {
|
|
122
|
+
const owned = new Set();
|
|
123
|
+
const re = /Core(?:Controller|Service)<\s*([A-Za-z0-9_]+)\s*>/g;
|
|
124
|
+
for (const content of contents) {
|
|
125
|
+
let m;
|
|
126
|
+
while ((m = re.exec(content)) !== null)
|
|
127
|
+
owned.add(m[1]);
|
|
128
|
+
}
|
|
129
|
+
return owned;
|
|
130
|
+
};
|
|
131
|
+
exports.modelsServedBySource = modelsServedBySource;
|
|
132
|
+
/** Scans src/modules for the models already served by an existing module. */
|
|
133
|
+
const ownedModels = () => {
|
|
134
|
+
const root = path.join(process.cwd(), 'src/modules');
|
|
135
|
+
if (!fs.existsSync(root))
|
|
136
|
+
return new Set();
|
|
137
|
+
const contents = [];
|
|
138
|
+
for (const dir of fs.readdirSync(root)) {
|
|
139
|
+
const dirPath = path.join(root, dir);
|
|
140
|
+
if (!fs.statSync(dirPath).isDirectory())
|
|
141
|
+
continue;
|
|
142
|
+
for (const file of fs.readdirSync(dirPath)) {
|
|
143
|
+
if (file.endsWith('.ts'))
|
|
144
|
+
contents.push(fs.readFileSync(path.join(dirPath, file), 'utf8'));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return (0, exports.modelsServedBySource)(contents);
|
|
148
|
+
};
|
|
149
|
+
exports.ownedModels = ownedModels;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export * from './common/guards/rbac.guard';
|
|
|
23
23
|
export * from './common/guards/user-population.guard';
|
|
24
24
|
export * from './modules/common.module';
|
|
25
25
|
export * from './common/interceptors/prisma.interceptor';
|
|
26
|
+
export * from './common/decorators/scalar-field.decorator';
|
|
26
27
|
export * from './common/config/permissionsConfigTypes';
|
|
27
28
|
export * from './common/config/adminConfigType';
|
|
28
29
|
export * from './common/config/permissions.config.provider';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAI3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAI3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,sCAAsC,CAAC;AACrD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uCAAuC,CAAC;AACtD,cAAc,yBAAyB,CAAC;AACxC,cAAc,0CAA0C,CAAC;AACzD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,oBAAoB,CAAC;AACnC,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,0CAA0C,CAAC;AACzD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wCAAwC,CAAC;AACvD,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qCAAqC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ __exportStar(require("./common/guards/rbac.guard"), exports);
|
|
|
42
42
|
__exportStar(require("./common/guards/user-population.guard"), exports);
|
|
43
43
|
__exportStar(require("./modules/common.module"), exports);
|
|
44
44
|
__exportStar(require("./common/interceptors/prisma.interceptor"), exports);
|
|
45
|
+
__exportStar(require("./common/decorators/scalar-field.decorator"), exports);
|
|
45
46
|
__exportStar(require("./common/config/permissionsConfigTypes"), exports);
|
|
46
47
|
__exportStar(require("./common/config/adminConfigType"), exports);
|
|
47
48
|
__exportStar(require("./common/config/permissions.config.provider"), exports);
|
package/docs/README.md
CHANGED
|
@@ -4,6 +4,7 @@ Authoritative usage documentation for `@appxdigital/appx-core`. **This folder is
|
|
|
4
4
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
7
|
+
- **[generate.md](./generate.md)** — the two code-generation commands: `generate` (deploy-safe — regenerates `src/generated/**` only, no code mutation) and `generate models` (the wizard that scaffolds + registers CRUD modules for chosen models). When to use each, and why not every table needs a module.
|
|
7
8
|
- **[permissions.md](./permissions.md)** — the authorization model: the `@Permission` and `@ExposeModels` decorators, the `permissions.config.ts` shape, roles/actions, row-level `conditions`, field-level control, action fallbacks, and the `GUEST` (unauthenticated) flow.
|
|
8
9
|
- **[dtos.md](./dtos.md)** — the generated CRUD validation DTOs: their two-file structure, **how to remove a field** (`OmitType` / `@NoWrite`), how to add validation, and why nested relation writes don't belong on generic CRUD.
|
|
9
10
|
- **[data-access.md](./data-access.md)** — how you talk to the database: the Prisma proxy (`prisma.model.*`), how ABAC filtering and field omission are enforced on every call, the redirected single-record methods, relation behaviour, bypass hatches, and transactions.
|
package/docs/dtos.md
CHANGED
|
@@ -34,7 +34,19 @@ A scalar field is **included** unless it is:
|
|
|
34
34
|
- annotated `/// @NoWrite`,
|
|
35
35
|
- annotated `/// @Role(none)`.
|
|
36
36
|
|
|
37
|
-
**Relation navigation fields are excluded**; their scalar foreign keys are kept.
|
|
37
|
+
**Relation navigation fields are excluded**; their scalar foreign keys are kept. Each field is typed to match its **Prisma model type** so a controller override type-checks, and validated accordingly:
|
|
38
|
+
|
|
39
|
+
| Prisma type | DTO type | Decorator | Wire input |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `String` / enum | `string` / enum | `@IsString` / `@IsEnum` | string |
|
|
42
|
+
| `Int` / `Float` | `number` | `@IsInt` / `@IsNumber` | number |
|
|
43
|
+
| `Boolean` | `boolean` | `@IsBoolean` | boolean |
|
|
44
|
+
| `DateTime` | `Date` | `@Type(() => Date)` | ISO-8601 string |
|
|
45
|
+
| `Decimal` | `Prisma.Decimal` | `@DecimalField()` | string **or** number |
|
|
46
|
+
| `BigInt` | `bigint` | `@BigIntField()` | string **or** number |
|
|
47
|
+
| `Json` / `Bytes` | `any` | `@Allow()` | any |
|
|
48
|
+
|
|
49
|
+
`@DecimalField()` / `@BigIntField()` accept a string or number and coerce/validate it (invalid → `400`); prefer strings for `Decimal` (money) and large `BigInt` values to avoid JSON float-precision loss. A `BigInt` column serialises as a **string** in JSON responses (JSON has no bigint). A `Decimal` column with the GraphQL generator requires the `prisma-graphql-type-decimal` peer dependency (npm auto-installs it).
|
|
38
50
|
|
|
39
51
|
> Because relations are excluded, generic CRUD endpoints accept **flat** writes (scalars + FK ids), not nested relation writes. See [Relations & nested writes](#relations--nested-writes) below.
|
|
40
52
|
|
package/docs/generate.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Code generation — `generate` and `generate models`
|
|
2
|
+
|
|
3
|
+
AppX Core generates code from your Prisma schema through **two commands with different responsibilities**. The split exists so that keeping generated types in sync (safe to automate) is separate from scaffolding CRUD modules (which writes code you own and edits `app.module.ts`).
|
|
4
|
+
|
|
5
|
+
| Command | What it does | Writes | Safe in CI? |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `appx-core generate` | Regenerate the Prisma client, GraphQL types, and DTO **base** classes | `src/generated/**` only (gitignored) | **Yes** — idempotent, no code mutation |
|
|
8
|
+
| `appx-core generate models` | Scaffold + register CRUD modules for chosen models | `src/modules/**` and `src/app.module.ts` | No — run during development |
|
|
9
|
+
|
|
10
|
+
## `appx-core generate` — the deploy-safe pass
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
appx-core generate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Runs `prisma generate` (which emits both the Prisma client and the prisma-nestjs-graphql artifacts) and regenerates the DTO base classes under `src/generated/dto/**`. It **never** writes `src/modules/**` and **never** edits `src/app.module.ts`, so it is safe to run in CI, a `postinstall`, or a predeploy step, and safe to re-run any number of times.
|
|
17
|
+
|
|
18
|
+
Run it **every time you change the Prisma schema** — it refreshes the client, GraphQL types, and DTO bases in one step (no separate `prisma generate` needed). Because everything it writes lives under the gitignored `src/generated/`, it never produces a diff in your committed code.
|
|
19
|
+
|
|
20
|
+
## `appx-core generate models` — the module wizard
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
appx-core generate models # interactive picker
|
|
24
|
+
appx-core generate models Habit Log # named models (Pascal or kebab)
|
|
25
|
+
appx-core generate models --all # every generatable model
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For each selected model it scaffolds the hand-owned files — module, service, controller, resolver, and the DTO subclass — **once** (never overwriting your edits), registers the module in `app.module.ts`, and then runs the deploy-safe pass so the new imports resolve.
|
|
29
|
+
|
|
30
|
+
The interactive picker lists **only models that don't already have a module**. If there are none, it prints `No modules available to generate`.
|
|
31
|
+
|
|
32
|
+
### You don't need a module for every table
|
|
33
|
+
|
|
34
|
+
Generated CRUD supports **permission-gated nested writes** (`create` / `connect`), so you can expose a parent resource and write related rows through it. Scaffold modules only for the resources you actually serve over HTTP/GraphQL — not mechanically for every table.
|
|
35
|
+
|
|
36
|
+
### `User` is framework-owned
|
|
37
|
+
|
|
38
|
+
The `User` model is never scaffolded. Authentication already serves user endpoints under `/auth/*`, and a generic `POST/PUT /users` CRUD is a mass-assignment surface. If you genuinely need generic user CRUD, write it by hand — but prefer a dedicated, permission-scoped endpoint.
|
|
39
|
+
|
|
40
|
+
### Ownership is by model, not folder name
|
|
41
|
+
|
|
42
|
+
Whether a model "already has a module" is determined by the model it serves (`CoreController<Model>` / `CoreService<Model>`), not by a folder-name convention. So a hand-written module under a non-canonical folder (e.g. a pluralised `wearable-connections/` serving `WearableConnection`) is recognised, and the wizard will **not** scaffold a duplicate.
|
|
43
|
+
|
|
44
|
+
## Typical workflow
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. Edit prisma/schema.prisma, then:
|
|
48
|
+
npx prisma migrate dev # apply the schema change
|
|
49
|
+
appx-core generate # refresh client / GraphQL / DTO bases (safe)
|
|
50
|
+
appx-core generate models # expose the new table(s) as CRUD (pick them)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
In CI / deploy, run **only** `appx-core generate` — it will never touch your committed code.
|
|
54
|
+
|
|
55
|
+
## GraphQL
|
|
56
|
+
|
|
57
|
+
Scaffolded modules include a **read-only** GraphQL resolver (query + aggregate; no mutations). See [graphql.md](./graphql.md). Per-module GraphQL toggling is planned for a later release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appxdigital/appx-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.123",
|
|
4
4
|
"description": "Appx Core is a library that provides a set of tools to help you build your application faster.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"helmet": "^8.3.0",
|
|
47
47
|
"jest": "^29.7.0",
|
|
48
48
|
"prettier": "^3.8.3",
|
|
49
|
+
"prisma-graphql-type-decimal": "^3.0.0",
|
|
49
50
|
"rimraf": "^6.1.2",
|
|
50
51
|
"supertest": "^7.2.2",
|
|
51
52
|
"testcontainers": "^10.28.0",
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
"passport-local": "~1.0.0",
|
|
95
96
|
"prisma": "~6.5.0",
|
|
96
97
|
"prisma-case-format": "~2.2.1",
|
|
98
|
+
"prisma-graphql-type-decimal": "^3.0.0",
|
|
97
99
|
"prisma-nestjs-graphql": "21.1.1",
|
|
98
100
|
"reflect-metadata": "~0.2.0",
|
|
99
101
|
"rxjs": "~7.8.1"
|