@alevnyacow/nzmt 0.15.4 → 0.15.5
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/README.md +179 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,121 +4,219 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# What
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Next.js tools you actually missed + a scaffolder for server logic & client queries. Not a framework. Full-stack, batteries included. ⚡
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Why
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- ☕ Keep using plain Next.js — just faster and cleaner. Skip the moment when some “helpful” framework fights you, making you wonder if coding it yourself would’ve been easier.
|
|
14
|
+
- 🧙 Focus on your domain logic without drowning in full-blown DDD.
|
|
15
|
+
- ✨ DI, Zod validation, project structure & API controllers out of the box.
|
|
16
|
+
- 🪄 Services, controllers, client queries, and other programmer stuff appear at the snap of a finger — and yes, it’s fun. (Well, not *literally* at the snap of a finger — that’s just marketing, to be honest. You still need to run one CLI command.)
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
# Quick start with Prisma
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
- Keep using plain Next.js, just faster and cleaner — no extra framework required.
|
|
19
|
-
- Watch entities, stores, services, controllers, handlers, and client-side queries appear automatically (and yes, it’s actually fun).
|
|
20
|
+
Example with CRUD API for User entity with react queries (assuming you have a Next.js project with a generated Prisma client `User` prisma schema and configured `@tanstack/react-query`).
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
```bash
|
|
23
|
+
# 1. Install NZMT and dependencies
|
|
24
|
+
npm i inversify zod reflect-metadata @alevnyacow/nzmt
|
|
25
|
+
|
|
26
|
+
# 2. Enable decorators in tsconfig.json
|
|
27
|
+
# {
|
|
28
|
+
# "compilerOptions": {
|
|
29
|
+
# "experimentalDecorators": true,
|
|
30
|
+
# "emitDecoratorMetadata": true
|
|
31
|
+
# }
|
|
32
|
+
# }
|
|
33
|
+
|
|
34
|
+
# 3. Initialize NZMT (once)
|
|
35
|
+
npx nzmt init prismaClientPath:@/app/generated/prisma/client
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
# 4. Scaffold CRUD API for `User` entity
|
|
38
|
+
npx nzmt crud-api user
|
|
39
|
+
```
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Let's break down what's been scaffolded.
|
|
42
|
+
|
|
43
|
+
### 1. Scaffolded `UserEntity`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
/** /shared/entities/user/user.entity.ts **/
|
|
47
|
+
|
|
48
|
+
import z from 'zod'
|
|
49
|
+
import { ValueObjects } from '@alevnyacow/nzmt'
|
|
50
|
+
|
|
51
|
+
export type UserModel = z.infer<typeof User.schema>
|
|
52
|
+
|
|
53
|
+
export class User {
|
|
54
|
+
static schema = z.object({
|
|
55
|
+
id: ValueObjects.Identifier.schema,
|
|
56
|
+
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
private constructor(private readonly data: UserModel) {}
|
|
60
|
+
|
|
61
|
+
static create = (data: UserModel) => {
|
|
62
|
+
const parsedModel = User.schema.parse(data)
|
|
63
|
+
return new User(parsedModel)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get model(): UserModel {
|
|
67
|
+
return this.data
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
26
71
|
|
|
27
|
-
|
|
72
|
+
All that’s left is to define the entity’s structure and validation in the static field `schema` (Zod). All related types are already derived, so contracts update automatically. Every scaffolded source file is fully editable, so you're in full control - you can add fields, methods, etc..
|
|
28
73
|
|
|
29
|
-
|
|
30
|
-
npm install inversify zod reflect-metadata @alevnyacow/nzmt
|
|
31
|
-
```
|
|
74
|
+
### 2. Scaffolded `UserStore` contract with `RAM` (in-memory) and `Prisma` implementations
|
|
32
75
|
|
|
33
|
-
|
|
76
|
+
We'll cover two of generated files that need your attention. First is a general user store description.
|
|
34
77
|
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
"compilerOptions": {
|
|
38
|
-
"experimentalDecorators": true,
|
|
39
|
-
"emitDecoratorMetadata": true
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
```
|
|
78
|
+
```ts
|
|
79
|
+
/** /server/stores/users/user.store.ts **/
|
|
43
80
|
|
|
44
|
-
|
|
81
|
+
import { Store } from '@alevnyacow/nzmt'
|
|
82
|
+
import { User } from '@/shared/entities/user'
|
|
45
83
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
84
|
+
export const userStoreMetadata = {
|
|
85
|
+
models: {
|
|
86
|
+
list: User.schema,
|
|
87
|
+
details: User.schema,
|
|
88
|
+
},
|
|
49
89
|
|
|
50
|
-
|
|
90
|
+
searchPayload: {
|
|
91
|
+
list: User.schema.omit({ id: true }),
|
|
92
|
+
specific: User.schema.pick({ id: true }),
|
|
93
|
+
},
|
|
51
94
|
|
|
52
|
-
|
|
95
|
+
actionsPayload: {
|
|
96
|
+
create: User.schema.omit({ id: true }),
|
|
97
|
+
update: User.schema.omit({ id: true }).partial(),
|
|
98
|
+
},
|
|
53
99
|
|
|
54
|
-
|
|
55
|
-
|
|
100
|
+
name: 'UserStore'
|
|
101
|
+
} satisfies Store.Metadata
|
|
102
|
+
|
|
103
|
+
export const { schemas: userStoreSchemas } = Store.toModuleMetadata(userStoreMetadata)
|
|
104
|
+
|
|
105
|
+
export type UserStore = Store.Contract<typeof userStoreMetadata>
|
|
56
106
|
```
|
|
57
107
|
|
|
58
|
-
|
|
108
|
+
Contracts are pretty self-explainatory, but let's break this down anyway.
|
|
109
|
+
|
|
110
|
+
Models:
|
|
111
|
+
|
|
112
|
+
- `models.list` - list model, `list` method will return list of those entities
|
|
113
|
+
- `models.details` - details model, `details` will return this entity
|
|
114
|
+
|
|
115
|
+
Search payload:
|
|
116
|
+
|
|
117
|
+
- `searchPayload.list` - how to filter data in `list` method
|
|
118
|
+
- `searchPayload.specific` - how to find one specific entity in `details` method
|
|
119
|
+
|
|
120
|
+
Actions payload:
|
|
121
|
+
|
|
122
|
+
- `actionsPayload.create` - what is needed to create a new entity
|
|
123
|
+
- `actionsPayload.update` - what is needed to update an entity. Note: only update payload must be here, filters will be used from `searchPayload`
|
|
124
|
+
|
|
125
|
+
All of these schemas can be modified. They don’t even have to be derived from the entity’s schema, but doing so is strongly recommended for consistency.
|
|
126
|
+
|
|
127
|
+
Second file is a `Prisma` implementation:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
/** /server/stores/users/user.store.prisma */
|
|
131
|
+
|
|
132
|
+
import type { Prisma, PrismaClient } from '@/app/generated/prisma/client'
|
|
133
|
+
import { DITokens } from '@/server/di'
|
|
134
|
+
import { injectable, inject } from 'inversify'
|
|
135
|
+
import { Store } from '@alevnyacow/nzmt'
|
|
136
|
+
import { type UserStore, userStoreMetadata } from './user.store'
|
|
137
|
+
|
|
138
|
+
type Types = Store.Types<UserStore>
|
|
139
|
+
|
|
140
|
+
const mappers = {
|
|
141
|
+
toFindOnePayload: (source: Types['findOnePayload']): Prisma.UserWhereUniqueInput => {
|
|
142
|
+
return {
|
|
143
|
+
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
toFindListPayload: (source: Types['findListPayload']): Prisma.UserWhereInput => {
|
|
147
|
+
return {
|
|
148
|
+
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
toListModel: (source: Prisma.UserGetPayload<{}>): Types['listModel'] => {
|
|
152
|
+
return {
|
|
153
|
+
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
toDetails: (source: Prisma.UserGetPayload<{ include: { } }>): Types['details'] => {
|
|
157
|
+
return {
|
|
158
|
+
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
toCreatePayload: (source: Types['createPayload']): Prisma.UserCreateInput => {
|
|
162
|
+
return {
|
|
163
|
+
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
toUpdatePayload: (source: Types['updatePayload']): Prisma.UserUpdateInput => {
|
|
167
|
+
return {
|
|
168
|
+
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@injectable()
|
|
174
|
+
export class UserPrismaStore implements UserStore {
|
|
175
|
+
..scaffolded implementation goes here
|
|
176
|
+
```
|
|
59
177
|
|
|
60
|
-
- `
|
|
61
|
-
- `UserStore` contract with `RAM` and `Prisma` implementations
|
|
62
|
-
- `UserService` with all business methods
|
|
63
|
-
- `UserController` with ready-to-use API endpoints
|
|
64
|
-
- Fully typed `UserAPI` contract (endpoints + DTOs) for client usage
|
|
65
|
-
- API `route handlers` inside your `app` folder
|
|
66
|
-
- `React queries`. Fully typed and ready to be used in your client-side code!
|
|
178
|
+
Pretty cool, right? All you need to do is implementing `mappers` and in the vast majority of cases it's enough to have working `Prisma` store. And even more - `RAM` implementation works out of the box! ✨
|
|
67
179
|
|
|
68
|
-
|
|
180
|
+
### 3. Scaffolded `UserService` with all business methods
|
|
69
181
|
|
|
70
|
-
|
|
182
|
+
Works out of the box. ✨
|
|
71
183
|
|
|
72
|
-
|
|
184
|
+
### 4. Scaffolded `UserController` with ready-to-use API endpoints
|
|
73
185
|
|
|
74
|
-
|
|
75
|
-
All methods and contracts are already scaffolded; you only need to describe the mappers themselves. RAM store implementation works out of the box.
|
|
186
|
+
Works out of the box. ✨
|
|
76
187
|
|
|
77
|
-
|
|
188
|
+
### 5. Scaffolded infrastructure helpers as `PrismaClient`, `Logger` and client util for handy requests.
|
|
78
189
|
|
|
79
|
-
|
|
190
|
+
### 6. Scaffolded `Route handlers` in `/app/api`
|
|
80
191
|
|
|
81
|
-
|
|
82
|
-
either adopt heavy architectural concepts or build with little to no structure at all. **Mature engineering is about trade-offs. A good tool should help you make them intentionally.**
|
|
192
|
+
Works out of the box. ✨
|
|
83
193
|
|
|
84
|
-
|
|
194
|
+
### 7. Scaffolded `React queries` for all controller methods
|
|
85
195
|
|
|
86
|
-
|
|
196
|
+
Also works out of the box! ✨
|
|
87
197
|
|
|
88
|
-
|
|
198
|
+
Let's take a look at `client/shared/queries/user-controller/GET.ts` for example:
|
|
89
199
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
200
|
+
```ts
|
|
201
|
+
import { useQuery } from '@tanstack/react-query'
|
|
202
|
+
import type { UserAPI } from '@/server/controllers/user'
|
|
203
|
+
import { apiRequest } from '@/client/shared/utils'
|
|
94
204
|
|
|
95
|
-
|
|
205
|
+
type Method = UserAPI['endpoints']['GET']
|
|
96
206
|
|
|
97
|
-
|
|
207
|
+
const endpoint = '/api/user-controller'
|
|
98
208
|
|
|
99
|
-
|
|
100
|
-
|
|
209
|
+
export const useUserController_GET = (payload: Method['payload']) => {
|
|
210
|
+
return useQuery<Method['response'], Method['error']>({
|
|
211
|
+
queryKey: [endpoint, payload],
|
|
212
|
+
queryFn: () => apiRequest(endpoint, 'GET')(payload)
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
```
|
|
101
216
|
|
|
217
|
+
- Fully typed and ready for client-side use.
|
|
218
|
+
- `apiRequest` handles endpoint, method, and payload conveniently (also scaffolded and editable).
|
|
102
219
|
|
|
103
|
-
|
|
220
|
+
**All code is editable - you stay in full control! 🔨⚙️**
|
|
104
221
|
|
|
105
|
-
|
|
106
|
-
- `methods` function
|
|
107
|
-
- `Metadata` type
|
|
108
|
-
- `DTOs` type
|
|
109
|
-
- `Methods` type
|
|
110
|
-
- `Config` type
|
|
111
|
-
- Controller
|
|
112
|
-
- `endpoints` function
|
|
113
|
-
- `DefaultErrorCodes` enum
|
|
114
|
-
- `Guard` type
|
|
115
|
-
- `OnErrorHandler` type
|
|
116
|
-
- `SharedConfig` type
|
|
117
|
-
- `Metadata` type
|
|
118
|
-
- `Contract` type
|
|
119
|
-
- Store
|
|
120
|
-
- `methods` function
|
|
121
|
-
- `InRAM` class generator
|
|
122
|
-
- `Types` type
|
|
123
|
-
- `Metadata` type
|
|
124
|
-
- `Contract` type
|
|
222
|
+
Now you can start building your domain logic — NZMT handles the boilerplate for you. 🪄
|