@sentzunhat/zacatl 0.0.4 → 0.0.6
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 +139 -144
- package/context.yaml +151 -0
- package/guidelines.yaml +74 -0
- package/mongodb.yaml +29 -0
- package/package.json +10 -10
- package/patterns.yaml +55 -0
- package/src/error/custom.ts +0 -1
- package/src/micro-service/architecture/infrastructure/repositories/abstract.ts +82 -20
- package/start.md +34 -0
- package/test/unit/micro-service/architecture/infrastructure/repositories/abstract.test.ts +6 -9
- package/src/errors.ts +0 -72
package/README.md
CHANGED
|
@@ -1,48 +1,112 @@
|
|
|
1
|
-
# Zacatl
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm install @sentzunhat/zacatl
|
|
37
|
-
# or
|
|
38
|
-
yarn add @sentzunhat/zacatl
|
|
39
|
-
# or
|
|
40
|
-
bun install @sentzunhat/zacatl
|
|
1
|
+
# Zacatl Microservice Framework
|
|
2
|
+
|
|
3
|
+
A blazing fast, modular TypeScript microservice framework for Node.js, designed for rapid development of high-performance APIs and distributed systems. Zacatl enforces a clean, layered architecture, robust typing, and seamless collaboration between human and AI contributors.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. How Humans and AI Agents Can Contribute
|
|
8
|
+
|
|
9
|
+
- **Read the docs:** Always review `guidelines.yaml`, `context.yaml`, and `patterns.yaml` before making changes.
|
|
10
|
+
- **Follow conventions:** Use dependency injection (`tsyringe`), maintain modularity, and respect the layered architecture.
|
|
11
|
+
- **Testing:** All logic must be unit tested (`vitest`). Place tests in `test/unit/`, mirroring the `src/` structure.
|
|
12
|
+
- **Documentation:** Update YAML docs with new patterns, features, or conventions. AI agents should parse YAML for context.
|
|
13
|
+
- **Register everything:** All services, repositories, and handlers must be registered in the DI container.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Project Structure & Layer Overview
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
src/
|
|
21
|
+
configuration.ts # App configuration (env, settings)
|
|
22
|
+
logs.ts # Logging utilities
|
|
23
|
+
optionals.ts # Optional helpers/utilities
|
|
24
|
+
error/ # Custom error classes (BadRequest, NotFound, etc.)
|
|
25
|
+
locales/ # Localization files (en.json, fr.json, ...)
|
|
26
|
+
micro-service/
|
|
27
|
+
architecture/
|
|
28
|
+
application/ # Entry points, route/hook handlers, validation
|
|
29
|
+
domain/ # Business logic, models, providers, services
|
|
30
|
+
infrastructure/ # DB repositories, adapters
|
|
31
|
+
platform/ # Service orchestration, DI setup
|
|
32
|
+
index.ts # Microservice layer entry
|
|
33
|
+
utils/ # Utility functions (base64, hmac, etc.)
|
|
41
34
|
```
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
- **Application:** Handles HTTP entry points, validation, and delegates to domain logic.
|
|
37
|
+
- **Domain:** Pure business logic, models, and providers. No infrastructure dependencies.
|
|
38
|
+
- **Infrastructure:** Persistence (MongoDB repositories), adapters, and external integrations.
|
|
39
|
+
- **Platform:** Bootstraps the app, configures DI, and starts the server.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Core Architectural Concepts
|
|
44
|
+
|
|
45
|
+
- **Layered (Hexagonal) Architecture:**
|
|
46
|
+
- Platform → Application → Domain → Infrastructure
|
|
47
|
+
- Strict separation of concerns; each layer has a single responsibility.
|
|
48
|
+
- **Error Handling:** All errors extend `CustomError` (see `src/error/`).
|
|
49
|
+
- **Validation:** Use `zod` schemas for all request/response validation.
|
|
50
|
+
- **Service Registration:** Register handlers/services in the DI container.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 4. Coding Standards for Humans and AI
|
|
55
|
+
|
|
56
|
+
- **Naming:**
|
|
57
|
+
- Files/folders: lowercase, hyphens (e.g., `user-repository.ts`)
|
|
58
|
+
- Classes: PascalCase
|
|
59
|
+
- Functions/variables: camelCase, descriptive
|
|
60
|
+
- **Code Style:**
|
|
61
|
+
- Clean, modular, and straightforward
|
|
62
|
+
- Use strong typing and TypeScript generics
|
|
63
|
+
- DRY principle: extract reusable logic
|
|
64
|
+
- Minimal comments; use tests to document behavior
|
|
65
|
+
- Validate all inputs and sanitize external data
|
|
66
|
+
- **AI Patterns:**
|
|
67
|
+
- Replicate DI, modularity, and test structure
|
|
68
|
+
- Update YAML docs when generating new code
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 5. Dependency Injection (DI) Details
|
|
73
|
+
|
|
74
|
+
- **DI Container:** All services, repositories, and handlers are registered using `tsyringe`.
|
|
75
|
+
- **No direct instantiation:** Always resolve dependencies via DI.
|
|
76
|
+
- **AI Registration:** When generating new components, ensure they are registered in the DI container.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 6. Database Schema & Persistence
|
|
81
|
+
|
|
82
|
+
- **MongoDB schemas:** Define all schemas in `src/micro-service/architecture/infrastructure/repositories/`.
|
|
83
|
+
- **Repository pattern:** Extend `BaseRepository` for new collections.
|
|
84
|
+
- **Schema best practices:**
|
|
85
|
+
- Favor embedding for related data
|
|
86
|
+
- Keep schemas minimal and property names concise
|
|
87
|
+
- Use references for large or independent data
|
|
88
|
+
- See `mongodb.yaml` for full guidelines
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 7. Error Management and Validation
|
|
93
|
+
|
|
94
|
+
- **Error Classes:** All errors extend `CustomError` (see `src/error/`).
|
|
95
|
+
- **Throw, don’t return:** Always throw errors for exceptional cases.
|
|
96
|
+
- **Validation:** Use `zod` for all request/response validation. Place schemas in the Application layer.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 8. Testing Philosophy and Setup
|
|
44
101
|
|
|
45
|
-
|
|
102
|
+
- **Framework:** Use `vitest` for all tests.
|
|
103
|
+
- **Structure:** Place tests in `test/unit/`, mirroring the `src/` structure.
|
|
104
|
+
- **Mocking:** Mock all external dependencies (DB, services) in tests.
|
|
105
|
+
- **AI Testing:** When generating new logic, always add or update tests.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 9. Minimal Usage Example
|
|
46
110
|
|
|
47
111
|
```typescript
|
|
48
112
|
import Fastify from "fastify";
|
|
@@ -84,131 +148,62 @@ const microservice = new MicroService({
|
|
|
84
148
|
await microservice.start({ port: 9000 });
|
|
85
149
|
```
|
|
86
150
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import { z } from "zod";
|
|
91
|
-
import { GetRouteHandler } from "@sentzunhat/zacatl";
|
|
92
|
-
|
|
93
|
-
const UserSchema = z.object({
|
|
94
|
-
id: z.string(),
|
|
95
|
-
name: z.string(),
|
|
96
|
-
email: z.string().email(),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
type User = z.infer<typeof UserSchema>;
|
|
100
|
-
|
|
101
|
-
class GetUserHandler extends GetRouteHandler<unknown, User, unknown> {
|
|
102
|
-
constructor() {
|
|
103
|
-
super({
|
|
104
|
-
url: "/users/:id",
|
|
105
|
-
schema: {
|
|
106
|
-
response: {
|
|
107
|
-
200: UserSchema,
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async handler(request, reply): Promise<User> {
|
|
114
|
-
const userId = request.params.id;
|
|
115
|
-
// Fetch user from database, etc.
|
|
116
|
-
return {
|
|
117
|
-
id: userId,
|
|
118
|
-
name: "John Doe",
|
|
119
|
-
email: "john@example.com",
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Database Integration
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
import { Schema } from "mongoose";
|
|
129
|
-
import { singleton } from "tsyringe";
|
|
130
|
-
import { BaseRepository } from "@sentzunhat/zacatl";
|
|
131
|
-
|
|
132
|
-
interface Product {
|
|
133
|
-
name: string;
|
|
134
|
-
price: number;
|
|
135
|
-
description: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const productSchema = new Schema<Product>({
|
|
139
|
-
name: { type: String, required: true },
|
|
140
|
-
price: { type: Number, required: true },
|
|
141
|
-
description: { type: String, required: true },
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
@singleton()
|
|
145
|
-
export class ProductRepository extends BaseRepository<Product> {
|
|
146
|
-
constructor() {
|
|
147
|
-
super({ name: "Product", schema: productSchema });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async findByName(name: string) {
|
|
151
|
-
return this.model.findOne({ name }).exec();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Architecture
|
|
157
|
-
|
|
158
|
-
The framework is organized into four main layers:
|
|
159
|
-
|
|
160
|
-
- **Application**: REST entry points (routes and hooks) and i18n configuration
|
|
161
|
-
- **Domain**: Business logic providers and services
|
|
162
|
-
- **Infrastructure**: Repositories and external dependencies
|
|
163
|
-
- **Platform**: Service startup, database connections, and server configuration
|
|
164
|
-
|
|
165
|
-
Each layer is designed to be extensible and testable, supporting clean code principles.
|
|
151
|
+
---
|
|
166
152
|
|
|
167
|
-
## Configuration
|
|
153
|
+
## 10. Configuration and Environment Variables
|
|
168
154
|
|
|
169
|
-
- **Localization
|
|
170
|
-
- **Environment Variables
|
|
155
|
+
- **Localization:** Place locale JSON files in `src/locales/` (e.g., `en.json`)
|
|
156
|
+
- **Environment Variables:**
|
|
171
157
|
- `SERVICE_NAME` - Service name
|
|
172
158
|
- `NODE_ENV` - Environment (development, production, test)
|
|
173
159
|
- `CONNECTION_STRING` - Database connection string
|
|
174
160
|
|
|
175
|
-
|
|
161
|
+
---
|
|
176
162
|
|
|
177
|
-
|
|
163
|
+
## 11. Extending the Codebase (for Humans & AI)
|
|
178
164
|
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
- `
|
|
165
|
+
- Add new features by creating new handlers, services, or repositories in the appropriate layer.
|
|
166
|
+
- Register all new classes in the DI container.
|
|
167
|
+
- Write tests for all new logic.
|
|
168
|
+
- Update YAML docs (`context.yaml`, `guidelines.yaml`, `patterns.yaml`, `mongodb.yaml`) with new patterns or conventions.
|
|
183
169
|
|
|
184
|
-
|
|
170
|
+
---
|
|
185
171
|
|
|
186
|
-
|
|
172
|
+
## 12. Documentation References
|
|
187
173
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```
|
|
174
|
+
- [`context.yaml`](./context.yaml): Architecture, components, and project context
|
|
175
|
+
- [`guidelines.yaml`](./guidelines.yaml): Coding standards and best practices
|
|
176
|
+
- [`patterns.yaml`](./patterns.yaml): Design and usage patterns
|
|
177
|
+
- [`mongodb.yaml`](./mongodb.yaml): MongoDB schema guidelines
|
|
193
178
|
|
|
194
|
-
|
|
179
|
+
---
|
|
195
180
|
|
|
196
|
-
## Contributing
|
|
181
|
+
## 13. Contributing Workflow
|
|
197
182
|
|
|
198
|
-
|
|
183
|
+
### For Humans
|
|
199
184
|
|
|
200
185
|
1. Fork the repository
|
|
201
186
|
2. Create your feature branch: `git checkout -b feature/amazing-feature`
|
|
202
187
|
3. Commit your changes: `git commit -m 'Add some amazing feature'`
|
|
203
188
|
4. Push to the branch: `git push origin feature/amazing-feature`
|
|
204
189
|
5. Open a Pull Request
|
|
190
|
+
6. Ensure all tests pass and docs are updated
|
|
205
191
|
|
|
206
|
-
|
|
192
|
+
### For AI Agents
|
|
207
193
|
|
|
208
|
-
|
|
194
|
+
- Parse YAML docs for context and conventions
|
|
195
|
+
- Register new code in the DI container
|
|
196
|
+
- Add or update tests in `test/unit/`
|
|
197
|
+
- Update YAML docs with new features or patterns
|
|
209
198
|
|
|
210
199
|
---
|
|
211
200
|
|
|
212
|
-
|
|
201
|
+
## 14. License and Maintainers
|
|
202
|
+
|
|
203
|
+
- **License:** MIT License © 2025 Sentzunhat
|
|
204
|
+
- **Maintainer:** Diego Beltran ([LinkedIn](https://www.linkedin.com/in/diego-beltran))
|
|
205
|
+
- **Project URL:** [GitHub](https://github.com/sentzunhat/zacatl)
|
|
206
|
+
|
|
207
|
+
---
|
|
213
208
|
|
|
214
|
-
|
|
209
|
+
_For all details, see the YAML documentation files above. Zacatl is designed for seamless collaboration between humans and AI agents._
|
package/context.yaml
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Zacatl Project: Structured Context
|
|
2
|
+
|
|
3
|
+
components:
|
|
4
|
+
- name: MicroService
|
|
5
|
+
type: class
|
|
6
|
+
file: src/micro-service/architecture/platform/micro-service.ts
|
|
7
|
+
description: |
|
|
8
|
+
The main orchestrator for the microservice. Bootstraps Application, Domain, Infrastructure, and Service layers. Handles startup, dependency registration, and handler registration.
|
|
9
|
+
methods:
|
|
10
|
+
- name: constructor
|
|
11
|
+
description: Initializes all architecture layers from config.
|
|
12
|
+
- name: start
|
|
13
|
+
description: Starts the service, configures databases, registers handlers, and launches the HTTP server.
|
|
14
|
+
dependencies:
|
|
15
|
+
- Application
|
|
16
|
+
- Domain
|
|
17
|
+
- Infrastructure
|
|
18
|
+
- Service
|
|
19
|
+
|
|
20
|
+
- name: Application
|
|
21
|
+
type: class
|
|
22
|
+
file: src/micro-service/architecture/application/application.ts
|
|
23
|
+
description: |
|
|
24
|
+
Handles HTTP entry points (REST), validation, and delegates to domain logic. Registers route and hook handlers using DI.
|
|
25
|
+
methods:
|
|
26
|
+
- name: constructor
|
|
27
|
+
description: Configures i18n and stores entry point config.
|
|
28
|
+
- name: start
|
|
29
|
+
description: Registers and stores REST hook and route handlers.
|
|
30
|
+
dependencies:
|
|
31
|
+
- AbstractArchitecture
|
|
32
|
+
- HookHandler
|
|
33
|
+
- RouteHandler
|
|
34
|
+
|
|
35
|
+
- name: Infrastructure
|
|
36
|
+
type: class
|
|
37
|
+
file: src/micro-service/architecture/infrastructure/infrastructure.ts
|
|
38
|
+
description: |
|
|
39
|
+
Registers and manages repositories (e.g., MongoDB) for persistence. Uses DI for repository registration.
|
|
40
|
+
methods:
|
|
41
|
+
- name: constructor
|
|
42
|
+
description: Stores repository config.
|
|
43
|
+
- name: start
|
|
44
|
+
description: Registers repositories in DI container.
|
|
45
|
+
dependencies:
|
|
46
|
+
- AbstractArchitecture
|
|
47
|
+
|
|
48
|
+
- name: BaseRepository
|
|
49
|
+
type: abstract class
|
|
50
|
+
file: src/micro-service/architecture/infrastructure/repositories/abstract.ts
|
|
51
|
+
description: |
|
|
52
|
+
Abstract base for MongoDB repositories. Handles model creation, lazy initialization, and CRUD methods. Ensures returned documents have an id field.
|
|
53
|
+
methods:
|
|
54
|
+
- name: constructor
|
|
55
|
+
description: Sets up Mongoose model and schema.
|
|
56
|
+
- name: findById
|
|
57
|
+
description: Finds a document by id and returns a lean object with id.
|
|
58
|
+
- name: create
|
|
59
|
+
description: Creates a new document and returns a lean object with id.
|
|
60
|
+
- name: update
|
|
61
|
+
description: Updates a document by id and returns a lean object with id.
|
|
62
|
+
- name: delete
|
|
63
|
+
description: Deletes a document by id and returns a lean object with id.
|
|
64
|
+
dependencies:
|
|
65
|
+
- mongoose
|
|
66
|
+
- tsyringe
|
|
67
|
+
|
|
68
|
+
- name: AbstractArchitecture
|
|
69
|
+
type: abstract class
|
|
70
|
+
file: src/micro-service/architecture/architecture.ts
|
|
71
|
+
description: |
|
|
72
|
+
Provides generic dependency registration helpers for all architecture layers. Enforces a start() contract.
|
|
73
|
+
methods:
|
|
74
|
+
- name: registerDependencies
|
|
75
|
+
description: Registers an array of classes in the DI container.
|
|
76
|
+
- name: registerAndStoreDependencies
|
|
77
|
+
description: Registers classes and stores resolved instances in a provided array.
|
|
78
|
+
- name: start
|
|
79
|
+
description: Abstract method to be implemented by subclasses.
|
|
80
|
+
dependencies:
|
|
81
|
+
- tsyringe
|
|
82
|
+
|
|
83
|
+
- name: CustomError
|
|
84
|
+
type: class
|
|
85
|
+
file: src/error/custom.ts
|
|
86
|
+
description: |
|
|
87
|
+
Base class for all custom errors. Supports metadata, error codes, and unique IDs. Used for structured error handling.
|
|
88
|
+
methods:
|
|
89
|
+
- name: constructor
|
|
90
|
+
description: Initializes error with message, code, reason, metadata, and stack trace.
|
|
91
|
+
|
|
92
|
+
- name: getConfigOrThrow
|
|
93
|
+
type: function
|
|
94
|
+
file: src/configuration.ts
|
|
95
|
+
description: |
|
|
96
|
+
Retrieves configuration values from the config system. Throws if missing.
|
|
97
|
+
|
|
98
|
+
- name: defaultLogger
|
|
99
|
+
type: object
|
|
100
|
+
file: src/logs.ts
|
|
101
|
+
description: |
|
|
102
|
+
Pino-based logger configured with environment and service metadata. Used for structured logging.
|
|
103
|
+
|
|
104
|
+
- name: Utility Exports
|
|
105
|
+
type: module
|
|
106
|
+
file: src/utils/index.ts
|
|
107
|
+
description: |
|
|
108
|
+
Exports utility functions (e.g., base64, hmac) for use throughout the codebase.
|
|
109
|
+
|
|
110
|
+
layers:
|
|
111
|
+
- name: Platform
|
|
112
|
+
description: Bootstraps the app, configures DI, starts the server.
|
|
113
|
+
files:
|
|
114
|
+
- src/micro-service/architecture/platform/micro-service.ts
|
|
115
|
+
- src/micro-service/architecture/platform/service/service.ts
|
|
116
|
+
- name: Application
|
|
117
|
+
description: Handles HTTP requests, validation, delegates to domain logic.
|
|
118
|
+
files:
|
|
119
|
+
- src/micro-service/architecture/application/application.ts
|
|
120
|
+
- src/micro-service/architecture/application/entry-points/rest/route-handlers/
|
|
121
|
+
- src/micro-service/architecture/application/entry-points/rest/hook-handlers/
|
|
122
|
+
- name: Domain
|
|
123
|
+
description: Pure business rules, models, and logic (no infrastructure deps).
|
|
124
|
+
files:
|
|
125
|
+
- src/micro-service/architecture/domain/
|
|
126
|
+
- name: Infrastructure
|
|
127
|
+
description: Persistence (MongoDB repositories), external service integration.
|
|
128
|
+
files:
|
|
129
|
+
- src/micro-service/architecture/infrastructure/repositories/
|
|
130
|
+
- src/micro-service/architecture/infrastructure/infrastructure.ts
|
|
131
|
+
|
|
132
|
+
patterns:
|
|
133
|
+
- Dependency Injection: All services, repositories, and handlers are registered in the DI container (tsyringe). Never instantiate dependencies directly.
|
|
134
|
+
- Error Handling: Custom error classes extend CustomError. Errors are thrown and bubble up to Fastify's error handler unless custom handling is needed.
|
|
135
|
+
- Validation: All request/response validation uses zod schemas.
|
|
136
|
+
- Testing: All business logic, handlers, and utilities are unit tested using vitest. Tests mirror the src/ structure.
|
|
137
|
+
|
|
138
|
+
config:
|
|
139
|
+
- Localization: Locale JSON files in src/locales/ (e.g., en.json)
|
|
140
|
+
- Environment Variables:
|
|
141
|
+
- SERVICE_NAME: Service name
|
|
142
|
+
- NODE_ENV: Environment (development, production, test)
|
|
143
|
+
- CONNECTION_STRING: Database connection string
|
|
144
|
+
- APP_VERSION: Application version
|
|
145
|
+
- APP_ENV: Application environment
|
|
146
|
+
|
|
147
|
+
testing:
|
|
148
|
+
- All tests are in test/unit/, mirroring src/ structure.
|
|
149
|
+
- Uses vitest for all tests.
|
|
150
|
+
- Mocks external dependencies (DB, services) in tests.
|
|
151
|
+
- Test helpers in test/unit/helpers/.
|
package/guidelines.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Zacatl Project: Coding Guidelines & Best Practices
|
|
2
|
+
|
|
3
|
+
naming:
|
|
4
|
+
file_folder:
|
|
5
|
+
- Use lowercase and hyphens (e.g., user-repository.ts, route-handlers/)
|
|
6
|
+
variables:
|
|
7
|
+
- Use meaningful, descriptive names
|
|
8
|
+
classes:
|
|
9
|
+
- Use PascalCase for class names
|
|
10
|
+
functions:
|
|
11
|
+
- Use camelCase for function and method names
|
|
12
|
+
|
|
13
|
+
style:
|
|
14
|
+
- Write clean, modular, and straightforward code
|
|
15
|
+
- Use strong typing and TypeScript generics where appropriate
|
|
16
|
+
- Prefer async/await for asynchronous code
|
|
17
|
+
- Keep functions short, focused, and readable
|
|
18
|
+
- Prefer clarity over excessive comments; use tests to document behavior
|
|
19
|
+
- Avoid code duplication (DRY principle); extract reusable logic into utils/ or shared services
|
|
20
|
+
- Use modern TypeScript/ES features
|
|
21
|
+
|
|
22
|
+
architecture:
|
|
23
|
+
- Follow layered (hexagonal) architecture: Platform → Application → Domain → Infrastructure
|
|
24
|
+
- Strict separation of concerns between layers
|
|
25
|
+
- Application: Handles HTTP, validation, delegates to domain
|
|
26
|
+
- Domain: Pure business logic, no infrastructure dependencies
|
|
27
|
+
- Infrastructure: Persistence, external integrations
|
|
28
|
+
- Platform: Bootstraps, configures DI, starts server
|
|
29
|
+
|
|
30
|
+
patterns:
|
|
31
|
+
dependency_injection:
|
|
32
|
+
- Use tsyringe for all DI
|
|
33
|
+
- Register all services, repositories, and handlers in the DI container
|
|
34
|
+
- Never instantiate dependencies directly; always resolve via DI
|
|
35
|
+
error_handling:
|
|
36
|
+
- All custom errors extend CustomError (src/error/custom.ts)
|
|
37
|
+
- Use CustomError.handle(error) in middleware or route handlers to log/format errors
|
|
38
|
+
- Always include relevant metadata and a clear message in error instances
|
|
39
|
+
- Throw errors for exceptional cases; do not return error objects
|
|
40
|
+
- Let errors bubble up to Fastify’s error handler unless custom handling is needed
|
|
41
|
+
validation:
|
|
42
|
+
- Use zod schemas for all request/response validation
|
|
43
|
+
testing:
|
|
44
|
+
- Use vitest for all tests
|
|
45
|
+
- Place tests in test/, mirroring src/ structure
|
|
46
|
+
- Write unit tests for all logic
|
|
47
|
+
- Mock all external dependencies (DB, services) in tests
|
|
48
|
+
- Use descriptive test names and group related tests in files named after the module under test
|
|
49
|
+
- Run tests using the vitest CLI
|
|
50
|
+
documentation:
|
|
51
|
+
- Update context.yaml and guidelines.yaml with any new patterns, conventions, or architectural changes
|
|
52
|
+
- Document new features, patterns, or conventions in YAML, not Markdown
|
|
53
|
+
|
|
54
|
+
mongodb:
|
|
55
|
+
- Define all schemas in infrastructure/repositories/
|
|
56
|
+
- Embed related data for performance when possible
|
|
57
|
+
- Keep schemas minimal and use concise property names
|
|
58
|
+
- Use references for large or independent data
|
|
59
|
+
- Ensure documents stay well below MongoDB’s 16MB limit
|
|
60
|
+
- Define indexes for frequently queried fields
|
|
61
|
+
- Use Mongoose and zod for schema and runtime validation
|
|
62
|
+
- Consider schema versioning for breaking changes
|
|
63
|
+
|
|
64
|
+
extending:
|
|
65
|
+
- Add new features by creating new handlers, services, or repositories in the appropriate layer
|
|
66
|
+
- Register all new classes in the DI container
|
|
67
|
+
- Write tests for all new logic
|
|
68
|
+
- Update context.yaml and guidelines.yaml with new patterns or conventions
|
|
69
|
+
|
|
70
|
+
security:
|
|
71
|
+
- Validate all inputs
|
|
72
|
+
- Handle errors gracefully
|
|
73
|
+
- Never expose sensitive data
|
|
74
|
+
- Sanitize and validate all external data
|
package/mongodb.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Zacatl Project: MongoDB Schema Design Guidelines
|
|
2
|
+
|
|
3
|
+
origin: mongodb_schema_design_guidelines.md
|
|
4
|
+
source: MongoDB_schema_design_guidelines.pdf
|
|
5
|
+
|
|
6
|
+
principles:
|
|
7
|
+
- favor_embedding:
|
|
8
|
+
rule: Embed related data within a single document when possible to improve performance by reducing the need for joins/queries.
|
|
9
|
+
rationale: Embedded documents ensure related data is retrieved together, enhancing read performance for one-to-few/one-to-many relationships.
|
|
10
|
+
- minimalism_and_simplicity:
|
|
11
|
+
rule: Design schemas with simplicity and minimalism. Use nested objects to reflect natural data hierarchies.
|
|
12
|
+
rationale: Minimal schemas are easier to understand, maintain, and scale.
|
|
13
|
+
- property_naming:
|
|
14
|
+
rule: Use single-word property names when possible; otherwise, limit to two words.
|
|
15
|
+
rationale: Short, concise property names reduce errors and improve readability.
|
|
16
|
+
- document_size_and_cardinality:
|
|
17
|
+
rule: Keep document size within MongoDB’s 16MB limit. Avoid embedding data that could grow unbounded (high-cardinality relationships).
|
|
18
|
+
rationale: Large documents degrade performance and complicate replication/backup. Use references for large/independent data.
|
|
19
|
+
- lifecycle_consistency:
|
|
20
|
+
rule: Embed data only if it shares a lifecycle with the parent document. Use references if embedded data is accessed/updated independently.
|
|
21
|
+
rationale: Aligning lifecycles ensures consistent, predictable schema behavior.
|
|
22
|
+
|
|
23
|
+
flexibility:
|
|
24
|
+
- These guidelines are best practices and should be adapted to specific application requirements and data access patterns.
|
|
25
|
+
- The recommendation for concise property names is flexible, but clarity and consistency are critical.
|
|
26
|
+
|
|
27
|
+
conclusion:
|
|
28
|
+
- Following these guidelines will help ensure MongoDB schemas are optimized for performance, maintainability, and scalability.
|
|
29
|
+
- Adapt as needed for your application’s needs.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@sentzunhat/zacatl",
|
|
3
3
|
"main": "src/index.ts",
|
|
4
4
|
"module": "src/index.ts",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.6",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/sentzunhat/zacatl.git"
|
|
@@ -45,18 +45,18 @@
|
|
|
45
45
|
},
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@fastify/http-proxy": "^
|
|
49
|
-
"config": "^
|
|
50
|
-
"fastify": "^5.
|
|
48
|
+
"@fastify/http-proxy": "^11.1.2",
|
|
49
|
+
"config": "^4.0.0",
|
|
50
|
+
"fastify": "^5.3.3",
|
|
51
51
|
"fastify-type-provider-zod": "^4.0.2",
|
|
52
52
|
"i18n": "^0.15.1",
|
|
53
53
|
"mongodb-memory-server": "^10.1.4",
|
|
54
|
-
"mongoose": "^8.
|
|
55
|
-
"pino": "^9.
|
|
56
|
-
"pino-pretty": "^
|
|
54
|
+
"mongoose": "^8.15.0",
|
|
55
|
+
"pino": "^9.7.0",
|
|
56
|
+
"pino-pretty": "^13.0.0",
|
|
57
57
|
"reflect-metadata": "^0.2.2",
|
|
58
|
-
"tsyringe": "^4.
|
|
59
|
-
"uuid": "^
|
|
60
|
-
"zod": "^3.
|
|
58
|
+
"tsyringe": "^4.10.0",
|
|
59
|
+
"uuid": "^11.1.0",
|
|
60
|
+
"zod": "^3.25.28"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/patterns.yaml
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Zacatl Project: Coding Patterns and Architectural Practices
|
|
2
|
+
|
|
3
|
+
origin:
|
|
4
|
+
- coding_standards.md
|
|
5
|
+
- coding_standards.pdf
|
|
6
|
+
- README.md (patterns section)
|
|
7
|
+
|
|
8
|
+
patterns:
|
|
9
|
+
- hexagonal_architecture:
|
|
10
|
+
description: |
|
|
11
|
+
Structure the application to separate core business logic from external dependencies using Ports and Adapters. Core logic is in the Domain layer, with Application, Infrastructure, and Platform layers handling entry points, persistence, and orchestration.
|
|
12
|
+
source: coding_standards.pdf
|
|
13
|
+
- object_oriented_programming:
|
|
14
|
+
description: |
|
|
15
|
+
Use classes to encapsulate state and behavior, especially for providers, repositories, and server classes. Follows OOP principles for modularity and encapsulation.
|
|
16
|
+
source: coding_standards.pdf
|
|
17
|
+
- functional_programming:
|
|
18
|
+
description: |
|
|
19
|
+
Use pure functions and higher-order functions for stateless operations and utility logic. Prefer immutability and function composition where possible.
|
|
20
|
+
source: coding_standards.pdf
|
|
21
|
+
- dependency_injection:
|
|
22
|
+
description: |
|
|
23
|
+
Use tsyringe for DI. Register all services, repositories, and handlers in the DI container. Never instantiate dependencies directly; always resolve via DI. Promotes modularity, flexibility, and testability.
|
|
24
|
+
source: coding_standards.pdf
|
|
25
|
+
- modular_server_design:
|
|
26
|
+
description: |
|
|
27
|
+
Server classes (e.g., ModularServer, FastifyServer) accept dependencies (Fastify, providers, databases, etc.) via constructor injection. Route registration and DI setup are centralized.
|
|
28
|
+
source: coding_standards.pdf
|
|
29
|
+
- centralized_configuration:
|
|
30
|
+
description: |
|
|
31
|
+
Use a DI container to register and configure dependencies (providers, repositories, Fastify instances, etc.). Server initialization is performed by resolving the server from the DI container and starting it on a specified port.
|
|
32
|
+
source: coding_standards.pdf
|
|
33
|
+
- error_handling:
|
|
34
|
+
description: |
|
|
35
|
+
All custom errors extend CustomError. Use CustomError.handle(error) in middleware or route handlers to log/format errors. Always include relevant metadata and a clear message. Let errors bubble up to Fastify’s error handler unless custom handling is needed.
|
|
36
|
+
source: README.md
|
|
37
|
+
- validation:
|
|
38
|
+
description: |
|
|
39
|
+
Use zod schemas for all request/response validation. Place validation logic in the Application layer.
|
|
40
|
+
source: README.md
|
|
41
|
+
- testing:
|
|
42
|
+
description: |
|
|
43
|
+
Use vitest for all tests. Place tests in test/, mirroring src/ structure. Write unit tests for all logic. Mock all external dependencies (DB, services) in tests. Use descriptive test names and group related tests in files named after the module under test.
|
|
44
|
+
source: README.md
|
|
45
|
+
|
|
46
|
+
best_practices:
|
|
47
|
+
- clean_code:
|
|
48
|
+
description: Write clean, modular, and straightforward code with meaningful names and strong typing. Use the latest language features and avoid redundancy (DRY principle).
|
|
49
|
+
source: coding_standards.pdf
|
|
50
|
+
- security_and_ethics:
|
|
51
|
+
description: Validate all inputs, handle errors gracefully, and never expose sensitive data. Code should be secure, efficient, and ethical.
|
|
52
|
+
source: coding_standards.pdf
|
|
53
|
+
- minimal_comments:
|
|
54
|
+
description: Prefer clarity in code and tests over excessive comments. Use comments only when necessary for context.
|
|
55
|
+
source: coding_standards.pdf
|
package/src/error/custom.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import importedMongoose, {
|
|
2
2
|
connection,
|
|
3
|
+
IfAny,
|
|
3
4
|
Model,
|
|
4
5
|
Mongoose,
|
|
6
|
+
Document,
|
|
5
7
|
Schema,
|
|
8
|
+
Default__v,
|
|
9
|
+
Require_id,
|
|
6
10
|
} from "mongoose";
|
|
7
11
|
import { v4 as uuidv4 } from "uuid";
|
|
8
12
|
import { container } from "tsyringe";
|
|
@@ -12,12 +16,32 @@ export type BaseRepositoryConfig<D> = {
|
|
|
12
16
|
schema: Schema<D>;
|
|
13
17
|
};
|
|
14
18
|
|
|
19
|
+
export type WithMongooseMeta<T> = Default__v<Require_id<T>>;
|
|
20
|
+
|
|
21
|
+
export type LeanWithMeta<T> = WithMongooseMeta<T> & {
|
|
22
|
+
id: string;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
};
|
|
26
|
+
|
|
15
27
|
export type LeanDocument<T> = T & {
|
|
16
28
|
id: string;
|
|
17
29
|
createdAt: Date;
|
|
18
30
|
updatedAt: Date;
|
|
19
31
|
};
|
|
20
32
|
|
|
33
|
+
export type MongooseDoc<Db> = IfAny<
|
|
34
|
+
Db,
|
|
35
|
+
unknown,
|
|
36
|
+
Document<unknown, {}, Db, {}> & WithMongooseMeta<Db>
|
|
37
|
+
>;
|
|
38
|
+
|
|
39
|
+
export type ToLeanInput<D, T> =
|
|
40
|
+
| MongooseDoc<D>
|
|
41
|
+
| LeanDocument<T>
|
|
42
|
+
| null
|
|
43
|
+
| undefined;
|
|
44
|
+
|
|
21
45
|
// D - Document, meant for Database representation
|
|
22
46
|
// T - Type, meant for TypeScript representation
|
|
23
47
|
export type Repository<D, T> = {
|
|
@@ -31,42 +55,88 @@ export type Repository<D, T> = {
|
|
|
31
55
|
|
|
32
56
|
export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
33
57
|
public readonly model: Model<D>;
|
|
58
|
+
private readonly config: BaseRepositoryConfig<D>;
|
|
34
59
|
|
|
35
60
|
constructor(config: BaseRepositoryConfig<D>) {
|
|
61
|
+
this.config = config;
|
|
36
62
|
const mongoose = connection.db?.databaseName
|
|
37
63
|
? importedMongoose
|
|
38
64
|
: container.resolve<Mongoose>(Mongoose);
|
|
39
|
-
|
|
40
|
-
const { name, schema } = config;
|
|
41
|
-
|
|
65
|
+
const { name, schema } = this.config;
|
|
42
66
|
if (name) {
|
|
43
67
|
this.model = mongoose.model<D>(name, schema);
|
|
44
68
|
} else {
|
|
45
69
|
this.model = mongoose.model<D>(uuidv4(), schema);
|
|
46
70
|
}
|
|
47
|
-
|
|
48
71
|
this.model.createCollection();
|
|
49
72
|
this.model.createIndexes();
|
|
50
73
|
this.model.init();
|
|
51
74
|
}
|
|
52
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Ensures the returned document has the correct id, createdAt, and updatedAt fields.
|
|
78
|
+
* Accepts a Mongoose document, a plain object, or a result from .lean().
|
|
79
|
+
*/
|
|
80
|
+
private toLean(input: ToLeanInput<D, T>): LeanDocument<T> | null {
|
|
81
|
+
if (!input) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let base: LeanWithMeta<T>;
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
Object.prototype.hasOwnProperty.call(input, "toObject") &&
|
|
89
|
+
typeof (input as unknown as { toObject: unknown }).toObject === "function"
|
|
90
|
+
) {
|
|
91
|
+
base = (
|
|
92
|
+
input as Document<unknown, {}, D, {}> & WithMongooseMeta<D>
|
|
93
|
+
).toObject<LeanDocument<T>>({ virtuals: true });
|
|
94
|
+
} else {
|
|
95
|
+
base = input as LeanWithMeta<T>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result: LeanDocument<T> = {
|
|
99
|
+
...(base as T), // trust only known keys from T
|
|
100
|
+
id:
|
|
101
|
+
typeof base.id === "string"
|
|
102
|
+
? base.id
|
|
103
|
+
: typeof base._id === "string"
|
|
104
|
+
? base._id
|
|
105
|
+
: base._id !== undefined
|
|
106
|
+
? String(base._id)
|
|
107
|
+
: "",
|
|
108
|
+
createdAt:
|
|
109
|
+
base.createdAt instanceof Date
|
|
110
|
+
? base.createdAt
|
|
111
|
+
: base.createdAt
|
|
112
|
+
? new Date(base.createdAt as string | number)
|
|
113
|
+
: new Date(),
|
|
114
|
+
updatedAt:
|
|
115
|
+
base.updatedAt instanceof Date
|
|
116
|
+
? base.updatedAt
|
|
117
|
+
: base.updatedAt
|
|
118
|
+
? new Date(base.updatedAt as string | number)
|
|
119
|
+
: new Date(),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
53
125
|
async findById(id: string): Promise<LeanDocument<T> | null> {
|
|
54
126
|
const entity = await this.model
|
|
55
127
|
.findById(id)
|
|
56
128
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
57
129
|
.exec();
|
|
58
130
|
|
|
59
|
-
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return entity;
|
|
131
|
+
return this.toLean(entity);
|
|
64
132
|
}
|
|
65
133
|
|
|
66
134
|
async create(entity: D): Promise<LeanDocument<T>> {
|
|
67
135
|
const doc = await this.model.create(entity);
|
|
68
136
|
|
|
69
|
-
|
|
137
|
+
doc.toObject();
|
|
138
|
+
|
|
139
|
+
return this.toLean(doc)!;
|
|
70
140
|
}
|
|
71
141
|
|
|
72
142
|
async update(
|
|
@@ -78,11 +148,7 @@ export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
|
78
148
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
79
149
|
.exec();
|
|
80
150
|
|
|
81
|
-
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return entity;
|
|
151
|
+
return this.toLean(entity);
|
|
86
152
|
}
|
|
87
153
|
|
|
88
154
|
async delete(id: string): Promise<LeanDocument<T> | null> {
|
|
@@ -91,10 +157,6 @@ export abstract class BaseRepository<D, T> implements Repository<D, T> {
|
|
|
91
157
|
.lean<LeanDocument<T>>({ virtuals: true })
|
|
92
158
|
.exec();
|
|
93
159
|
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return entity;
|
|
160
|
+
return this.toLean(entity);
|
|
99
161
|
}
|
|
100
162
|
}
|
package/start.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Zacatl Project Onboarding & Usage
|
|
2
|
+
|
|
3
|
+
Welcome to Zacatl! This project uses structured YAML documentation for all context, architecture, coding standards, and best practices.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
- **Project context, architecture, and component summaries:**
|
|
8
|
+
See [`context.yaml`](./context.yaml)
|
|
9
|
+
- **Coding standards, naming conventions, and best practices:**
|
|
10
|
+
See [`guidelines.yaml`](./guidelines.yaml)
|
|
11
|
+
- **Design and usage patterns:**
|
|
12
|
+
See [`patterns.yaml`](./patterns.yaml)
|
|
13
|
+
- **MongoDB schema design guidelines:**
|
|
14
|
+
See [`mongodb.yaml`](./mongodb.yaml)
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
1. Install dependencies:
|
|
19
|
+
```zsh
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
2. Run tests:
|
|
23
|
+
```zsh
|
|
24
|
+
npm test
|
|
25
|
+
```
|
|
26
|
+
3. Explore the codebase, starting from `src/`.
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
- Follow the guidelines in `guidelines.yaml` and `patterns.yaml`.
|
|
31
|
+
- Update `context.yaml`, `guidelines.yaml`, `patterns.yaml`, or `mongodb.yaml` with any new patterns or conventions.
|
|
32
|
+
- Place all tests in the `test/` directory, mirroring the `src/` structure.
|
|
33
|
+
|
|
34
|
+
For any questions, refer to the YAML documentation or contact the maintainers.
|
|
@@ -13,7 +13,7 @@ const schemaUserTest = new Schema<UserTest>({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
@singleton()
|
|
16
|
-
class UserRepository extends BaseRepository<UserTest> {
|
|
16
|
+
class UserRepository extends BaseRepository<UserTest, UserTest> {
|
|
17
17
|
constructor() {
|
|
18
18
|
super({ name: "User", schema: schemaUserTest });
|
|
19
19
|
}
|
|
@@ -46,13 +46,11 @@ describe("BaseRepository", () => {
|
|
|
46
46
|
|
|
47
47
|
it("should call findById() and return the user document", async () => {
|
|
48
48
|
const user = await repository.create({ name: "Alice" });
|
|
49
|
-
|
|
50
49
|
const spyFunction = vi.spyOn(repository.model, "findById");
|
|
51
|
-
|
|
52
50
|
const result = await repository.findById(user.id);
|
|
53
|
-
|
|
54
51
|
expect(spyFunction).toHaveBeenNthCalledWith(1, user.id);
|
|
55
|
-
expect(result).
|
|
52
|
+
expect(result).toMatchObject({ name: user.name });
|
|
53
|
+
expect(result?.id).toBe(user.id);
|
|
56
54
|
});
|
|
57
55
|
|
|
58
56
|
it("should call update() and return the updated user document", async () => {
|
|
@@ -72,12 +70,11 @@ describe("BaseRepository", () => {
|
|
|
72
70
|
|
|
73
71
|
it("should call delete() and return the deleted user document", async () => {
|
|
74
72
|
const user = await repository.create({ name: "Alice" });
|
|
75
|
-
|
|
76
73
|
const spyFunction = vi.spyOn(repository.model, "findByIdAndDelete");
|
|
77
|
-
|
|
78
74
|
const result = await repository.delete(user.id);
|
|
79
|
-
|
|
80
75
|
expect(spyFunction).toHaveBeenNthCalledWith(1, user.id);
|
|
81
|
-
|
|
76
|
+
// Patch: allow for id only (ignore _id)
|
|
77
|
+
expect(result).toMatchObject({ name: user.name });
|
|
78
|
+
expect(result?.id).toBe(user.id);
|
|
82
79
|
});
|
|
83
80
|
});
|
package/src/errors.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { ZodError } from "zod";
|
|
2
|
-
import { isError } from "lodash";
|
|
3
|
-
|
|
4
|
-
import { logger } from "./logs";
|
|
5
|
-
import { FastifyError, FastifyReply, FastifyRequest } from "fastify";
|
|
6
|
-
|
|
7
|
-
const isZodError = (error: unknown): error is ZodError =>
|
|
8
|
-
isError(error) && "ZodError" === error.name;
|
|
9
|
-
|
|
10
|
-
const handleApiError = async (handler: () => Promise<void>) => {
|
|
11
|
-
try {
|
|
12
|
-
await handler();
|
|
13
|
-
} catch (err) {
|
|
14
|
-
const { request, response } = err as {
|
|
15
|
-
request: { data: unknown };
|
|
16
|
-
response: { data: unknown };
|
|
17
|
-
};
|
|
18
|
-
if (request)
|
|
19
|
-
logger.error("request", { logData: { request: { data: request.data } } });
|
|
20
|
-
if (response)
|
|
21
|
-
logger.error("response", {
|
|
22
|
-
logData: { response: { data: response.data } },
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
if (err) {
|
|
26
|
-
logger.error("errored", { logData: { error: err } });
|
|
27
|
-
throw err;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const handleRouteError = (
|
|
33
|
-
error: FastifyError,
|
|
34
|
-
request: FastifyRequest,
|
|
35
|
-
reply: FastifyReply
|
|
36
|
-
) => {
|
|
37
|
-
console.log("====================================");
|
|
38
|
-
console.log({ request });
|
|
39
|
-
console.log("====================================");
|
|
40
|
-
|
|
41
|
-
if (isZodError(error)) {
|
|
42
|
-
const zodError = error as ZodError;
|
|
43
|
-
const logDataZodError = {
|
|
44
|
-
name: zodError.name,
|
|
45
|
-
issues: zodError.issues,
|
|
46
|
-
};
|
|
47
|
-
return reply.status(400).send({ ok: false, error: logDataZodError });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (error instanceof SyntaxError) {
|
|
51
|
-
reply.status(400).send({ error: "Bad Request", message: error.message });
|
|
52
|
-
} else if (error instanceof ZodError) {
|
|
53
|
-
reply
|
|
54
|
-
.status(422)
|
|
55
|
-
.send({ error: "Validation Error", message: error.errors });
|
|
56
|
-
} else {
|
|
57
|
-
reply
|
|
58
|
-
.status(500)
|
|
59
|
-
.send({ error: "Internal Server Error", message: error.message });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return reply.status(400).send({
|
|
63
|
-
ok: false,
|
|
64
|
-
error: {
|
|
65
|
-
name: error.name,
|
|
66
|
-
message: error.message,
|
|
67
|
-
error,
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export { isZodError, handleApiError, handleRouteError };
|