@alevnyacow/nzmt 0.13.1 → 0.14.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 (3) hide show
  1. package/README.md +5 -75
  2. package/bin/cli.js +58 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,7 +36,6 @@ npm install inversify zod reflect-metadata @alevnyacow/nzmt
36
36
 
37
37
  2. Enable `Experimental decorators` and `Emit Decorator Metadata` options in your `tsconfig.json`.
38
38
  ```json
39
- // tsconfig.json
40
39
  {
41
40
  "compilerOptions": {
42
41
  "experimentalDecorators": true,
@@ -45,7 +44,7 @@ npm install inversify zod reflect-metadata @alevnyacow/nzmt
45
44
  }
46
45
  ```
47
46
 
48
- 3. Initialize NZMT. This will set up all required infrastructure and configuration for you:
47
+ 3. Initialize NZMT (must be done once). This will set up all required infrastructure and configuration for you:
49
48
  ```bash
50
49
  npx nzmt init prismaClientPath:@/app/generated/prisma/client
51
50
  ```
@@ -58,29 +57,17 @@ npx nzmt crud-api user
58
57
  This will generate:
59
58
 
60
59
  - `User` entity
61
- - `UserStore` contract, `UserRAMStore` and `UserPrismaStore` implementation
60
+ - `UserStore` contract, `UserRAMStore` and `UserPrismaStore` implementations
62
61
  - `UserService` proxying all `UserStore` methods
63
- - `UserController` proxying all `UserService` methods.
62
+ - `UserController` proxying all `UserService` methods
63
+ - API `route handlers` can be used from client
64
64
 
65
65
  All code is editable - you stay in full control.
66
66
 
67
67
  5. **Describe entity properties and validation rules using Zod** for the `User` entity in the scaffolded file `/shared/entities/user/user.entity.ts`.
68
68
 
69
69
  6. **Implement Prisma mappers** in `/server/stores/user/user.store.prisma.ts`.
70
- All methods and contracts are already scaffolded; you only need to describe the mappers themselves.
71
-
72
- 7. Use generated controller in `app/api/user/route.ts` file via DI.
73
-
74
- ```ts
75
- import type { UserController } from "@/server/controllers/user"
76
- import { fromDI } from "@/server/di"
77
-
78
- // Get a fully typed controller instance from the DI container.
79
- // Key is fully typed too, of course.
80
- const controller = fromDI<UserController>('UserController')
81
- // Use controller method as a route method.
82
- export const GET = controller.GET
83
- ```
70
+ All methods and contracts are already scaffolded; you only need to describe the mappers themselves. RAM store implementation works out of the box.
84
71
 
85
72
  # Design principles
86
73
 
@@ -107,63 +94,6 @@ There are also two building blocks shared across server and client: Entities and
107
94
  - **Entities** represent domain objects used throughout the application. They don’t include data access or business flow logic, but may contain pure domain logic, contracts and invariants (e.g. User, Product).
108
95
  - **Value Objects** define reusable, strongly-typed invariants for meaningful concepts such as Pagination or Identifier.
109
96
 
110
- # Scaffolding
111
-
112
- ## Setup
113
-
114
- ```
115
- npx nzmt init prismaClientPath:@/app/generated/prisma/client
116
- ```
117
- This creates `nzmt.config.json`, sets up DI and testing, and adds base providers. `prismaClientPath:...` parameter is optional and enables Prisma scaffolding.
118
-
119
- ## Naming conventions
120
-
121
- - The entity name is expected to be **in `kebab-case`** (e.g. `awesome-user`, `product`).
122
- - The entity name is expected to be **in singular form** (e.g. `product` instead of `products`).
123
-
124
- ## Shared layer modules
125
-
126
- ### Entities
127
-
128
- Example: scaffolding a `User` entity with two fields (name and age).
129
-
130
- ```bash
131
- npx nzmt entity user f:name-string,age-int.positive
132
- ```
133
-
134
- You can define entity fields using the `f:` flag. The format is `name-type`, where `type` maps to Zod (e.g. `int.positive` → `z.int().positive()`). This is optional — `npx nzmt entity user` will scaffold a `User` entity without additional fields.
135
-
136
- Entity scaffolder generates a dedicated folder with a barrel file and an entity implementation. Generated code is fully editable — you stay in control.
137
-
138
- The generated `user.entity.ts` looks like this:
139
-
140
- ```ts
141
- import z from 'zod'
142
- import { ValueObjects } from '@alevnyacow/nzmt'
143
-
144
- export type UserModel = z.infer<typeof User.schema>
145
-
146
- export class User {
147
- static schema = z.object({
148
- id: ValueObjects.Identifier.schema,
149
- name: z.string(),
150
- age: z.int().positive(),
151
- })
152
-
153
- private constructor(private readonly data: UserModel) {}
154
-
155
- static create = (data: UserModel) => {
156
- const parsedModel = User.schema.parse(data)
157
- return new User(parsedModel)
158
- }
159
-
160
- get model(): UserModel {
161
- return this.data
162
- }
163
- }
164
- ```
165
-
166
- `User` entity, `User.schema` zod schema and `UserModel` type can be used wherever they are needed.
167
97
 
168
98
  # Package API
169
99
 
package/bin/cli.js CHANGED
@@ -980,6 +980,63 @@ function generateController(upperCase, lowerCase, crudService) {
980
980
  )
981
981
  }
982
982
 
983
+ function generateAPIRoutes(lowerCase, upperCase) {
984
+ const projectRoot = findProjectRoot()
985
+ const fileText = fs.readFileSync(
986
+ path.resolve(projectRoot, `${config.coreFolder}${config.paths.controllers}`, entityName, `${entityName}.controller.ts`),
987
+ 'utf-8'
988
+ )
989
+
990
+ const regex = /^\s*(\w+)\s*=\s*this\.endpoints/mg
991
+ const methods = Array.from(fileText.matchAll(regex), m => m[1])
992
+
993
+ const methodInfo = methods.map(method => ({method: method.split('_').pop(), path: method.split('_').slice(0, -1).join('/')}))
994
+
995
+ const rootMethods = methodInfo.filter(x => !x.path.length).map(x => x.method)
996
+ const nestedMethods = methodInfo.filter(x => !!x.path.length).reduce((acc, cur) => {
997
+ if (!acc[cur.path]) {
998
+ acc[cur.path] = []
999
+ }
1000
+ acc[cur.path].push(cur.method)
1001
+ return acc
1002
+ }, {})
1003
+
1004
+ const controllerHandlersRootPath = path.resolve(projectRoot, config.coreFolder, 'app', 'api', `${entityName}-controller`)
1005
+
1006
+ fs.mkdirSync(controllerHandlersRootPath, { recursive: true })
1007
+
1008
+ if (rootMethods.length) {
1009
+ fs.writeFileSync(path.resolve(controllerHandlersRootPath, 'route.ts'), [
1010
+ `import type { ${upperCase}Controller } from '@${config.paths.controllers}/${entityName}'`,
1011
+ `import { fromDI } from '@${config.paths.di}'`,
1012
+ '',
1013
+ `const controller = fromDI<${upperCase}Controller>('${upperCase}Controller')`,
1014
+ '',
1015
+ rootMethods.map(x => `export const ${x} = controller.${x}`).join('\n')
1016
+ ].join('\n'))
1017
+ }
1018
+
1019
+ for (const [currentPath, methods] of Object.entries(nestedMethods)) {
1020
+ const nestedFolder = path.resolve(controllerHandlersRootPath, currentPath)
1021
+ fs.mkdirSync(nestedFolder, { recursive: true })
1022
+ fs.writeFileSync(path.resolve(nestedFolder, 'route.ts'), [
1023
+ `import type { ${upperCase}Controller } from '@${config.paths.controllers}/${entityName}'`,
1024
+ `import { fromDI } from '@${config.paths.di}'`,
1025
+ '',
1026
+ `const controller = fromDI<${upperCase}Controller>('${upperCase}Controller')`,
1027
+ '',
1028
+ methods.map(x => `export const ${x} = controller.${currentPath.replaceAll('/', '_')}_${x}`).join('\n')
1029
+ ].join('\n'))
1030
+ }
1031
+ }
1032
+
1033
+
1034
+ if (command === 'api-routes') {
1035
+ var [lowerCase, upperCase] = camelizeVariants(entityName)
1036
+
1037
+ generateAPIRoutes(lowerCase, upperCase)
1038
+ }
1039
+
983
1040
  if (command.toLowerCase() === 'controller' || command === 'c') {
984
1041
  var [lowerCase, upperCase] = camelizeVariants(entityName)
985
1042
  generateController(upperCase, lowerCase)
@@ -1007,5 +1064,6 @@ if (command.toLowerCase() === 'crud-api') {
1007
1064
  generateStores(lowerCase, upperCase, true)
1008
1065
  generateService(lowerCase, upperCase, upperCase + 'Store')
1009
1066
  generateController(upperCase, lowerCase, upperCase + 'Service')
1067
+ generateAPIRoutes(lowerCase, upperCase)
1010
1068
  process.exit(0)
1011
1069
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alevnyacow/nzmt",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Next Zod Modules Toolkit",
5
5
  "repository": {
6
6
  "type": "git",