@alevnyacow/nzmt 0.13.2 → 0.14.1

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 -73
  2. package/bin/cli.js +58 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,9 +57,11 @@ npx nzmt crud-api user
57
57
  This will generate:
58
58
 
59
59
  - `User` entity
60
- - `UserStore` contract, `UserRAMStore` and `UserPrismaStore` implementations
61
- - `UserService` proxying all `UserStore` methods
62
- - `UserController` proxying all `UserService` methods.
60
+ - `UserStore` contract with `RAM` and `Prisma` implementations
61
+ - `UserService` with all business methods
62
+ - `UserController` with ready-to-use API endpoints
63
+ - Fully typed `UserAPI` contract (endpoints + DTOs) for client usage
64
+ - API `route handlers` inside your `app` folder
63
65
 
64
66
  All code is editable - you stay in full control.
65
67
 
@@ -68,19 +70,6 @@ All code is editable - you stay in full control.
68
70
  6. **Implement Prisma mappers** in `/server/stores/user/user.store.prisma.ts`.
69
71
  All methods and contracts are already scaffolded; you only need to describe the mappers themselves. RAM store implementation works out of the box.
70
72
 
71
- 7. Use generated controller in `app/api/user/route.ts` file via DI.
72
-
73
- ```ts
74
- import type { UserController } from "@/server/controllers/user"
75
- import { fromDI } from "@/server/di"
76
-
77
- // Get a fully typed controller instance from the DI container.
78
- // Key is fully typed too, of course.
79
- const controller = fromDI<UserController>('UserController')
80
- // Use controller method as a route method.
81
- export const GET = controller.GET
82
- ```
83
-
84
73
  # Design principles
85
74
 
86
75
  ## Core idea
@@ -106,63 +95,6 @@ There are also two building blocks shared across server and client: Entities and
106
95
  - **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).
107
96
  - **Value Objects** define reusable, strongly-typed invariants for meaningful concepts such as Pagination or Identifier.
108
97
 
109
- # Scaffolding
110
-
111
- ## Setup
112
-
113
- ```
114
- npx nzmt init prismaClientPath:@/app/generated/prisma/client
115
- ```
116
- This creates `nzmt.config.json`, sets up DI and testing, and adds base providers. `prismaClientPath:...` parameter is optional and enables Prisma scaffolding.
117
-
118
- ## Naming conventions
119
-
120
- - The entity name is expected to be **in `kebab-case`** (e.g. `awesome-user`, `product`).
121
- - The entity name is expected to be **in singular form** (e.g. `product` instead of `products`).
122
-
123
- ## Shared layer modules
124
-
125
- ### Entities
126
-
127
- Example: scaffolding a `User` entity with two fields (name and age).
128
-
129
- ```bash
130
- npx nzmt entity user f:name-string,age-int.positive
131
- ```
132
-
133
- 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.
134
-
135
- Entity scaffolder generates a dedicated folder with a barrel file and an entity implementation. Generated code is fully editable — you stay in control.
136
-
137
- The generated `user.entity.ts` looks like this:
138
-
139
- ```ts
140
- import z from 'zod'
141
- import { ValueObjects } from '@alevnyacow/nzmt'
142
-
143
- export type UserModel = z.infer<typeof User.schema>
144
-
145
- export class User {
146
- static schema = z.object({
147
- id: ValueObjects.Identifier.schema,
148
- name: z.string(),
149
- age: z.int().positive(),
150
- })
151
-
152
- private constructor(private readonly data: UserModel) {}
153
-
154
- static create = (data: UserModel) => {
155
- const parsedModel = User.schema.parse(data)
156
- return new User(parsedModel)
157
- }
158
-
159
- get model(): UserModel {
160
- return this.data
161
- }
162
- }
163
- ```
164
-
165
- `User` entity, `User.schema` zod schema and `UserModel` type can be used wherever they are needed.
166
98
 
167
99
  # Package API
168
100
 
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.2",
3
+ "version": "0.14.1",
4
4
  "description": "Next Zod Modules Toolkit",
5
5
  "repository": {
6
6
  "type": "git",