@sentzunhat/zacatl 0.0.0-alpha.9 → 0.0.3

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 CHANGED
@@ -1,64 +1,67 @@
1
- # zacatl
1
+ # Zacatl
2
2
 
3
- A blazing fast, minimal, and straightforward microservice framework for Node.js, designed for rapid development of high-performance APIs and distributed systems.
3
+ [![npm version](https://img.shields.io/npm/v/@sentzunhat/zacatl.svg)](https://www.npmjs.com/package/@sentzunhat/zacatl)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)
4
6
 
5
- ## Project Description
7
+ A blazing fast, minimal, and straightforward microservice framework for Node.js, designed for rapid development of high-performance APIs and distributed systems. Zacatl provides a clean, modular architecture with robust typing support, letting you focus on business logic instead of infrastructure concerns.
6
8
 
7
- **zacatl** (published as `@sentzunhat/zacatl`) is a TypeScript-first microservice library that enables you to bootstrap robust, scalable services with minimal configuration. Built on top of Fastify and Mongoose, it provides a modular architecture for defining application logic, infrastructure, and service orchestration, while supporting advanced features like dependency injection, localization, and flexible error handling.
9
+ ## Table of Contents
8
10
 
9
- ## Features
10
-
11
- - **Fastify-based HTTP server**: Ultra-fast routing and extensibility.
12
- - **Mongoose integration**: Easy MongoDB data modeling and repository pattern.
13
- - **Modular architecture**: Clean separation of application, domain, infrastructure, and platform layers.
14
- - **Dependency injection**: Powered by `tsyringe` for testability and flexibility.
15
- - **Gateway support**: Proxy requests to upstream services with minimal setup.
16
- - **Internationalization (i18n)**: Built-in support for multiple locales.
17
- - **Comprehensive error handling**: Custom error classes and route error utilities.
18
- - **Type-safe route and hook handlers**: Strongly-typed REST entry points using Zod and Fastify.
19
- - **Testing utilities**: Vitest configuration and helpers for fast, isolated unit tests.
20
-
21
- ## Architecture Overview
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Usage](#usage)
14
+ - [Architecture](#architecture)
15
+ - [Configuration](#configuration)
16
+ - [API Reference](#api-reference)
17
+ - [Testing](#testing)
18
+ - [Contributing](#contributing)
19
+ - [License](#license)
22
20
 
23
- The framework is organized into the following layers:
24
-
25
- - **Application**: Defines REST entry points (routes and hooks) and configures i18n.
26
- - **Domain**: Registers business logic providers.
27
- - **Infrastructure**: Manages repositories and external dependencies.
28
- - **Platform**: Orchestrates service startup, database connections, and server/gateway configuration.
21
+ ## Features
29
22
 
30
- Each layer is extensible and testable, supporting clean code and maintainability.
23
+ - **Fastify-based HTTP server**: Ultra-fast routing and extensibility
24
+ - **Mongoose integration**: Easy MongoDB data modeling and repository pattern
25
+ - **Modular architecture**: Clean separation of application, domain, infrastructure, and platform layers
26
+ - **Dependency injection**: Powered by `tsyringe` for testability and flexibility
27
+ - **Gateway support**: Proxy requests to upstream services with minimal setup
28
+ - **Internationalization (i18n)**: Built-in support for multiple locales
29
+ - **Comprehensive error handling**: Custom error classes and route error utilities
30
+ - **Type-safe route handlers**: Strongly-typed REST entry points using Zod and Fastify
31
+ - **Testing utilities**: Vitest configuration and helpers for fast, isolated unit tests
31
32
 
32
33
  ## Installation
33
34
 
34
35
  ```bash
35
- bun install
36
- # or
37
36
  npm install @sentzunhat/zacatl
37
+ # or
38
+ yarn add @sentzunhat/zacatl
39
+ # or
40
+ bun install @sentzunhat/zacatl
38
41
  ```
39
42
 
40
43
  ## Usage
41
44
 
42
- Here's how to quickly spin up a microservice:
45
+ ### Basic Setup
43
46
 
44
- ```ts
47
+ ```typescript
45
48
  import Fastify from "fastify";
46
49
  import { MicroService } from "@sentzunhat/zacatl";
47
50
 
48
51
  const fastifyApp = Fastify();
49
52
 
50
- const microServer = new MicroService({
53
+ const microservice = new MicroService({
51
54
  architecture: {
52
55
  application: {
53
56
  entryPoints: {
54
57
  rest: {
55
- hookHandlers: [], // Add your hook handler classes here
56
- routeHandlers: [], // Add your route handler classes here
58
+ hookHandlers: [], // Add hook handler classes
59
+ routeHandlers: [], // Add route handler classes
57
60
  },
58
61
  },
59
62
  },
60
- domain: { providers: [] }, // Add your domain provider classes here
61
- infrastructure: { repositories: [] }, // Add your repository classes here
63
+ domain: { providers: [] }, // Add domain provider classes
64
+ infrastructure: { repositories: [] }, // Add repository classes
62
65
  service: {
63
66
  name: "my-service",
64
67
  server: {
@@ -78,43 +81,134 @@ const microServer = new MicroService({
78
81
  },
79
82
  });
80
83
 
81
- microServer.start({ port: 9000 });
84
+ await microservice.start({ port: 9000 });
85
+ ```
86
+
87
+ ### Creating Route Handlers
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
+ }
82
154
  ```
83
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.
166
+
84
167
  ## Configuration
85
168
 
86
- - **Localization**: Place your locale JSON files in `src/locales/` (e.g., `en.json`, `fr.json`).
87
- - **Environment Variables**: Configure your database connection strings and other settings as needed.
88
- - **Custom Handlers**: Implement route and hook handlers by extending the provided abstract classes.
169
+ - **Localization**: Place locale JSON files in `src/locales/` (e.g., `en.json`)
170
+ - **Environment Variables**:
171
+ - `SERVICE_NAME` - Service name
172
+ - `NODE_ENV` - Environment (development, production, test)
173
+ - `CONNECTION_STRING` - Database connection string
174
+
175
+ ## API Reference
176
+
177
+ ### Core Components
178
+
179
+ - `MicroService`: Central orchestrator for all architecture layers
180
+ - `AbstractRouteHandler`: Base class for all route handlers
181
+ - `BaseRepository`: Base class for MongoDB repositories
182
+ - `CustomError`: Base class for typed error handling
89
183
 
90
184
  ## Testing
91
185
 
92
- Run all unit tests with coverage:
186
+ Run tests with:
93
187
 
94
188
  ```bash
95
- bun run test
96
- # or
97
189
  npm run test
190
+ # or
191
+ npm run test:coverage
98
192
  ```
99
193
 
100
- Test configuration uses Vitest with in-memory MongoDB for fast, isolated tests.
101
-
102
- ## API Reference
103
-
104
- - **Application Layer**: See `src/micro-service/architecture/application/`
105
- - **Domain Layer**: See `src/micro-service/architecture/domain/`
106
- - **Infrastructure Layer**: See `src/micro-service/architecture/infrastructure/`
107
- - **Platform Layer**: See `src/micro-service/architecture/platform/`
108
- - **Error Handling**: See `src/error/` for custom error classes and utilities.
194
+ Test configuration uses Vitest with in-memory MongoDB for isolated tests.
109
195
 
110
196
  ## Contributing
111
197
 
112
- Contributions are welcome! If you find this software useful and make improvements, please consider submitting a pull request. See the [LICENSE](./LICENSE) for details.
198
+ Contributions are welcome! Please feel free to submit a Pull Request.
199
+
200
+ 1. Fork the repository
201
+ 2. Create your feature branch: `git checkout -b feature/amazing-feature`
202
+ 3. Commit your changes: `git commit -m 'Add some amazing feature'`
203
+ 4. Push to the branch: `git push origin feature/amazing-feature`
204
+ 5. Open a Pull Request
113
205
 
114
206
  ## License
115
207
 
116
- MIT License © 2025 sentzunhat
208
+ MIT License © 2025 Sentzunhat - See the [LICENSE](./LICENSE) file for details.
117
209
 
118
210
  ---
119
211
 
120
- **zacatl** is built for speed, simplicity, and extensibility. For questions, issues, or feature requests, please open an issue on [GitHub](https://github.com/sentzunhat/zacatl).
212
+ Built by [Diego Beltran](https://sentzunhat.com) with [Fastify](https://fastify.io/), [Mongoose](https://mongoosejs.com/), and [tsyringe](https://github.com/microsoft/tsyringe).
213
+
214
+ For questions or issues, please visit [GitHub](https://github.com/sentzunhat/zacatl/issues).
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.0-alpha.9",
5
+ "version": "0.0.3",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/sentzunhat/zacatl.git"
@@ -1,10 +1,8 @@
1
1
  import importedMongoose, {
2
2
  connection,
3
3
  Model,
4
- Document,
5
4
  Mongoose,
6
5
  Schema,
7
- Types,
8
6
  } from "mongoose";
9
7
  import { v4 as uuidv4 } from "uuid";
10
8
  import { container } from "tsyringe";
@@ -14,26 +12,23 @@ export type BaseRepositoryConfig<T> = {
14
12
  schema: Schema<T>;
15
13
  };
16
14
 
17
- export type TDocument<T> = Document<Types.ObjectId, {}, T> &
18
- T & {
19
- id: string;
20
- };
21
-
22
- export type TRequiredId<T> = T & {
23
- _id: Types.ObjectId;
15
+ export type LeanDocument<T> = T & {
16
+ id: string;
17
+ createdAt: Date;
18
+ updatedAt: Date;
24
19
  };
25
20
 
26
21
  export type Repository<T> = {
27
22
  model: Model<T>;
28
23
 
29
- findById(id: string): Promise<TDocument<T> | null>;
30
- create(entity: T): Promise<TDocument<T>>;
31
- update(id: string, entity: Partial<T>): Promise<TDocument<T> | null>;
32
- delete(id: string): Promise<TDocument<T> | null>;
24
+ findById(id: string): Promise<LeanDocument<T> | null>;
25
+ create(entity: T): Promise<LeanDocument<T>>;
26
+ update(id: string, update: Partial<T>): Promise<LeanDocument<T> | null>;
27
+ delete(id: string): Promise<LeanDocument<T> | null>;
33
28
  };
34
29
 
35
30
  export abstract class BaseRepository<T> implements Repository<T> {
36
- public model: Model<T>;
31
+ public readonly model: Model<T>;
37
32
 
38
33
  constructor(config: BaseRepositoryConfig<T>) {
39
34
  const mongoose = connection.db?.databaseName
@@ -53,59 +48,51 @@ export abstract class BaseRepository<T> implements Repository<T> {
53
48
  this.model.init();
54
49
  }
55
50
 
56
- /**
57
- * Transforms a Mongoose document into a plain entity object.
58
- */
59
- protected toEntity(doc: TDocument<T>): TRequiredId<T> {
60
- // We call the built-in toObject method and cast to T.
61
- return doc.toObject<TDocument<T>>({ virtuals: true }) as TRequiredId<T>;
62
- }
63
-
64
- async findById(id: string): Promise<TDocument<T> | null> {
65
- const foundEntity = await this.model.findById<TDocument<T>>(id).exec();
51
+ async findById(id: string): Promise<LeanDocument<T> | null> {
52
+ const entity = await this.model
53
+ .findById(id)
54
+ .lean<LeanDocument<T>>({ virtuals: true })
55
+ .exec();
66
56
 
67
- if (!foundEntity) {
57
+ if (!entity) {
68
58
  return null;
69
59
  }
70
60
 
71
- return foundEntity.toObject<TDocument<T>>({
72
- virtuals: true,
73
- });
61
+ return entity;
74
62
  }
75
63
 
76
- async create(entity: T): Promise<TDocument<T>> {
77
- const createdEntity = await this.model.create<TDocument<T>>(entity);
64
+ async create(entity: T): Promise<LeanDocument<T>> {
65
+ const doc = await this.model.create(entity);
78
66
 
79
- return createdEntity.toObject<TDocument<T>>({
80
- virtuals: true,
81
- });
67
+ return doc.toObject<LeanDocument<T>>({ virtuals: true });
82
68
  }
83
69
 
84
- async update(id: string, entity: Partial<T>): Promise<TDocument<T> | null> {
85
- const updatedEntity = await this.model
86
- .findByIdAndUpdate<TDocument<T>>(id, entity, { new: true })
70
+ async update(
71
+ id: string,
72
+ update: Partial<T>
73
+ ): Promise<LeanDocument<T> | null> {
74
+ const entity = await this.model
75
+ .findByIdAndUpdate(id, update, { new: true })
76
+ .lean<LeanDocument<T>>({ virtuals: true })
87
77
  .exec();
88
78
 
89
- if (!updatedEntity) {
79
+ if (!entity) {
90
80
  return null;
91
81
  }
92
82
 
93
- return updatedEntity.toObject<TDocument<T>>({
94
- virtuals: true,
95
- });
83
+ return entity;
96
84
  }
97
85
 
98
- async delete(id: string): Promise<TDocument<T> | null> {
99
- const deletedEntity = await this.model
100
- .findByIdAndDelete<TDocument<T>>(id)
86
+ async delete(id: string): Promise<LeanDocument<T> | null> {
87
+ const entity = await this.model
88
+ .findByIdAndDelete(id)
89
+ .lean<LeanDocument<T>>({ virtuals: true })
101
90
  .exec();
102
91
 
103
- if (!deletedEntity) {
92
+ if (!entity) {
104
93
  return null;
105
94
  }
106
95
 
107
- return deletedEntity.toObject<TDocument<T>>({
108
- virtuals: true,
109
- });
96
+ return entity;
110
97
  }
111
98
  }
@@ -15,13 +15,3 @@ export const encodeBase64 = (input: string): string => {
15
15
  export const decodeBase64 = (input: string): string => {
16
16
  return Buffer.from(input, "base64").toString("utf-8");
17
17
  };
18
-
19
- /**
20
- * Encodes a token by prefixing it with "Bearer " and converting it to Base64 format.
21
- *
22
- * @param token - The raw token string to encode.
23
- * @returns The Base64-encoded token string with the "Bearer " prefix.
24
- */
25
- export const encodeToken = (token: string): string => {
26
- return encodeBase64(`Bearer ${token}`);
27
- };
package/src/nullable.ts DELETED
@@ -1 +0,0 @@
1
- export type Nullable<T> = T | undefined;
package/src/stringify.ts DELETED
@@ -1,11 +0,0 @@
1
- const stringify = (input: any): string => {
2
- if (Array.isArray(input)) {
3
- return input.map(stringify).join("");
4
- } else if (typeof input === "object" && input !== null) {
5
- return Object.values(input).map(stringify).join("");
6
- } else {
7
- return input;
8
- }
9
- };
10
-
11
- export { stringify };
package/src/summarize.ts DELETED
@@ -1,23 +0,0 @@
1
- // const summarize = async ({ context }: { context: string }): Promise<string> => {
2
- // const { env, pipeline } = await import("@xenova/transformers");
3
-
4
- // // cacheDir
5
- // env.cacheDir = "./.cache";
6
-
7
- // // Specify a custom location for models (defaults to '/models/').
8
- // env.localModelPath = "./.models/";
9
-
10
- // // Create a text-generation pipeline
11
- // const generator = await pipeline(
12
- // "text2text-generation",
13
- // "Xenova/LaMini-Flan-T5-783M"
14
- // );
15
-
16
- // const result = (await generator(`Summarize the following text: ${context}.`, {
17
- // max_new_tokens: 250,
18
- // })) as { generated_text: string }[];
19
-
20
- // return result[0]?.generated_text ?? "";
21
- // };
22
-
23
- // export { summarize };