@forgedevstack/harbor 1.0.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.
- package/CHANGELOG.md +126 -0
- package/README.md +927 -0
- package/dist/changelog/index.d.ts +3 -0
- package/dist/changelog/index.d.ts.map +1 -0
- package/dist/changelog/manager.d.ts +29 -0
- package/dist/changelog/manager.d.ts.map +1 -0
- package/dist/changelog/types.d.ts +41 -0
- package/dist/changelog/types.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +43 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/constants/config.const.d.ts +11 -0
- package/dist/constants/config.const.d.ts.map +1 -0
- package/dist/constants/defaults.const.d.ts +10 -0
- package/dist/constants/defaults.const.d.ts.map +1 -0
- package/dist/constants/http.const.d.ts +43 -0
- package/dist/constants/http.const.d.ts.map +1 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/validation.const.d.ts +35 -0
- package/dist/constants/validation.const.d.ts.map +1 -0
- package/dist/core/config.d.ts +6 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/errorHandler.d.ts +25 -0
- package/dist/core/errorHandler.d.ts.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/router.d.ts +68 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/server.d.ts +4 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/database/connection.d.ts +39 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/index.d.ts +28 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/model.d.ts +118 -0
- package/dist/database/model.d.ts.map +1 -0
- package/dist/database/schema.d.ts +63 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/types.d.ts +270 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/docker/index.d.ts +3 -0
- package/dist/docker/index.d.ts.map +1 -0
- package/dist/docker/index.js +2 -0
- package/dist/docker/index.js.map +1 -0
- package/dist/docker/manager.d.ts +21 -0
- package/dist/docker/manager.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +38 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/locales/en.d.ts +2 -0
- package/dist/i18n/locales/en.d.ts.map +1 -0
- package/dist/i18n/locales/he.d.ts +2 -0
- package/dist/i18n/locales/he.d.ts.map +1 -0
- package/dist/index.cjs.js +24 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +2094 -0
- package/dist/index.es.js.map +1 -0
- package/dist/logger-D7aJSi62.mjs +102 -0
- package/dist/logger-D7aJSi62.mjs.map +1 -0
- package/dist/logger-DEnWXtpk.js +3 -0
- package/dist/logger-DEnWXtpk.js.map +1 -0
- package/dist/manager-B1UKMjXW.js +4 -0
- package/dist/manager-B1UKMjXW.js.map +1 -0
- package/dist/manager-B6vqJgEn.mjs +152 -0
- package/dist/manager-B6vqJgEn.mjs.map +1 -0
- package/dist/portal.d.ts +13 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/types/config.types.d.ts +83 -0
- package/dist/types/config.types.d.ts.map +1 -0
- package/dist/types/docker.types.d.ts +92 -0
- package/dist/types/docker.types.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger.types.d.ts +35 -0
- package/dist/types/logger.types.d.ts.map +1 -0
- package/dist/types/route.types.d.ts +78 -0
- package/dist/types/route.types.d.ts.map +1 -0
- package/dist/types/server.types.d.ts +67 -0
- package/dist/types/server.types.d.ts.map +1 -0
- package/dist/types/validation.types.d.ts +64 -0
- package/dist/types/validation.types.d.ts.map +1 -0
- package/dist/utils/helpers.d.ts +7 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/httpLogger.d.ts +52 -0
- package/dist/utils/httpLogger.d.ts.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/object.d.ts +6 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/dist/validation/index.d.ts +5 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/mongo.d.ts +13 -0
- package/dist/validation/mongo.d.ts.map +1 -0
- package/dist/validation/paramValidators.d.ts +18 -0
- package/dist/validation/paramValidators.d.ts.map +1 -0
- package/dist/validation/validators.d.ts +9 -0
- package/dist/validation/validators.d.ts.map +1 -0
- package/harbor.config.example.json +72 -0
- package/package.json +107 -0
- package/templates/default/.eslintrc.json +45 -0
- package/templates/default/README.md +97 -0
- package/templates/default/constants/config.ts +26 -0
- package/templates/default/constants/http.ts +32 -0
- package/templates/default/constants/index.ts +3 -0
- package/templates/default/controllers/index.ts +6 -0
- package/templates/default/controllers/user.controller.ts +77 -0
- package/templates/default/env.example +22 -0
- package/templates/default/harbor.version.json +7 -0
- package/templates/default/models/index.ts +6 -0
- package/templates/default/models/user.model.ts +68 -0
- package/templates/default/package.json +44 -0
- package/templates/default/routes/index.ts +12 -0
- package/templates/default/routes/user.routes.ts +21 -0
- package/templates/default/server.ts +45 -0
- package/templates/default/services/index.ts +6 -0
- package/templates/default/services/user.service.ts +84 -0
- package/templates/default/tsconfig.json +35 -0
- package/templates/default/types/index.ts +57 -0
- package/templates/default/utils/asyncHandler.ts +14 -0
- package/templates/default/utils/index.ts +5 -0
- package/templates/default/utils/logger.ts +52 -0
- package/templates/default/utils/response.ts +24 -0
- package/templates/default/utils/validation.ts +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
# Harbor 🚢
|
|
2
|
+
|
|
3
|
+
**The Pipeline for Node.js Backends**
|
|
4
|
+
|
|
5
|
+
Harbor is a complete backend framework that replaces Express routing, Mongoose ODM, and more. Fast server creation, route management, MongoDB integration, validation, Docker orchestration, and automatic error handling — all in one lightweight, TypeScript-first package.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/harbor)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Server Setup](#server-setup)
|
|
17
|
+
- [MongoDB Connection](#mongodb-connection)
|
|
18
|
+
- [Schema & Models](#schema--models)
|
|
19
|
+
- [Route Management](#route-management)
|
|
20
|
+
- [Validation](#validation)
|
|
21
|
+
- [Error Handling](#error-handling)
|
|
22
|
+
- [Middleware](#middleware)
|
|
23
|
+
- [Docker Manager](#docker-manager)
|
|
24
|
+
- [Configuration](#configuration)
|
|
25
|
+
- [i18n (Translations)](#i18n-translations)
|
|
26
|
+
- [API Reference](#api-reference)
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- 🚀 **Fast Server Creation** - Create a production-ready server with one function call
|
|
31
|
+
- 🛣️ **Route Management** - Fluent API with pre/post middleware, validation, and automatic error handling
|
|
32
|
+
- 🍃 **MongoDB ODM** - Full Mongoose replacement with Schema, Model, and all query methods
|
|
33
|
+
- ✅ **Validation** - Powerful request validation with Mongoose-compatible schemas
|
|
34
|
+
- 🐳 **Docker Manager** - Build, push, pull images and orchestrate containers
|
|
35
|
+
- 🌍 **i18n Support** - Built-in internationalization for all messages
|
|
36
|
+
- 📝 **Morgan-like Logger** - HTTP request logging with customizable formats
|
|
37
|
+
- ⚙️ **Config-Driven** - Centralized configuration via `harbor.config.json`
|
|
38
|
+
- 📘 **TypeScript First** - Full type definitions with excellent IntelliSense
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# npm
|
|
44
|
+
npm install harbor
|
|
45
|
+
|
|
46
|
+
# yarn
|
|
47
|
+
yarn add harbor
|
|
48
|
+
|
|
49
|
+
# pnpm
|
|
50
|
+
pnpm add harbor
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Peer Dependencies
|
|
54
|
+
|
|
55
|
+
Harbor requires `mongodb` for database operations:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install mongodb
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### 1. Initialize Project
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx harbor init
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This creates:
|
|
70
|
+
- `harbor.config.json` - Configuration file
|
|
71
|
+
- `src/server.ts` - Basic server setup
|
|
72
|
+
|
|
73
|
+
### 2. Create Your Server
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { createServer, GET, POST, connect } from 'harbor';
|
|
77
|
+
|
|
78
|
+
// Connect to MongoDB
|
|
79
|
+
await connect('mongodb://localhost:27017/myapp');
|
|
80
|
+
|
|
81
|
+
// Create server
|
|
82
|
+
const server = createServer({ port: 3000 });
|
|
83
|
+
|
|
84
|
+
// Add routes
|
|
85
|
+
server.addRoute(
|
|
86
|
+
GET('/api/health', () => ({ status: 'ok', timestamp: new Date() }))
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
server.addRoute(
|
|
90
|
+
POST('/api/users', async (req) => {
|
|
91
|
+
const { email, name } = req.validated.body;
|
|
92
|
+
return { id: '123', email, name };
|
|
93
|
+
}, {
|
|
94
|
+
validation: {
|
|
95
|
+
body: {
|
|
96
|
+
email: { type: 'email', required: true },
|
|
97
|
+
name: { type: 'string', required: true, min: 2 }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Server is running at http://localhost:3000
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Run Your Server
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx ts-node src/server.ts
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Server Setup
|
|
113
|
+
|
|
114
|
+
### Basic Server
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { createServer } from 'harbor';
|
|
118
|
+
|
|
119
|
+
const server = createServer({
|
|
120
|
+
port: 3000, // Default: 3000
|
|
121
|
+
host: 'localhost', // Default: 'localhost'
|
|
122
|
+
configPath: './harbor.config.json',
|
|
123
|
+
autoStart: true, // Default: true
|
|
124
|
+
onReady: (info) => console.log(`Server ready on ${info.host}:${info.port}`),
|
|
125
|
+
onError: (error) => console.error('Server error:', error),
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Server Methods
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// Add routes
|
|
133
|
+
server.addRoute(route);
|
|
134
|
+
server.addRoutes([route1, route2]);
|
|
135
|
+
|
|
136
|
+
// Add middleware
|
|
137
|
+
server.addMiddleware(middleware);
|
|
138
|
+
|
|
139
|
+
// Get Express app instance
|
|
140
|
+
const app = server.getApp();
|
|
141
|
+
|
|
142
|
+
// Graceful shutdown
|
|
143
|
+
await server.stop();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## MongoDB Connection
|
|
147
|
+
|
|
148
|
+
Harbor provides a complete Mongoose replacement. All the methods you know from Mongoose are available.
|
|
149
|
+
|
|
150
|
+
### Connecting to MongoDB
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { connect, disconnect, connection } from 'harbor';
|
|
154
|
+
|
|
155
|
+
// Connect to MongoDB
|
|
156
|
+
await connect('mongodb://localhost:27017/myapp', {
|
|
157
|
+
maxPoolSize: 10,
|
|
158
|
+
serverSelectionTimeoutMS: 30000,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Connection events
|
|
162
|
+
connection.on('connected', () => console.log('Connected!'));
|
|
163
|
+
connection.on('disconnected', () => console.log('Disconnected!'));
|
|
164
|
+
connection.on('error', (err) => console.error('Error:', err));
|
|
165
|
+
|
|
166
|
+
// Check connection state
|
|
167
|
+
console.log(connection.readyState); // 0: disconnected, 1: connected, 2: connecting
|
|
168
|
+
|
|
169
|
+
// Disconnect
|
|
170
|
+
await disconnect();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Connection Options
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
await connect(uri, {
|
|
177
|
+
maxPoolSize: 10, // Maximum connections in pool
|
|
178
|
+
minPoolSize: 1, // Minimum connections
|
|
179
|
+
serverSelectionTimeoutMS: 30000,
|
|
180
|
+
socketTimeoutMS: 45000,
|
|
181
|
+
retryWrites: true,
|
|
182
|
+
w: 'majority',
|
|
183
|
+
authSource: 'admin',
|
|
184
|
+
replicaSet: 'rs0',
|
|
185
|
+
ssl: true,
|
|
186
|
+
tls: true,
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Schema & Models
|
|
191
|
+
|
|
192
|
+
### Defining a Schema
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { Schema, model } from 'harbor';
|
|
196
|
+
|
|
197
|
+
// Define schema (same as Mongoose!)
|
|
198
|
+
const userSchema = new Schema({
|
|
199
|
+
email: {
|
|
200
|
+
type: 'String',
|
|
201
|
+
required: true,
|
|
202
|
+
unique: true,
|
|
203
|
+
lowercase: true,
|
|
204
|
+
match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
205
|
+
},
|
|
206
|
+
password: {
|
|
207
|
+
type: 'String',
|
|
208
|
+
required: true,
|
|
209
|
+
minLength: 8
|
|
210
|
+
},
|
|
211
|
+
name: {
|
|
212
|
+
type: 'String',
|
|
213
|
+
required: true,
|
|
214
|
+
trim: true
|
|
215
|
+
},
|
|
216
|
+
role: {
|
|
217
|
+
type: 'String',
|
|
218
|
+
enum: ['user', 'admin', 'moderator'],
|
|
219
|
+
default: 'user'
|
|
220
|
+
},
|
|
221
|
+
age: {
|
|
222
|
+
type: 'Number',
|
|
223
|
+
min: 0,
|
|
224
|
+
max: 150
|
|
225
|
+
},
|
|
226
|
+
isActive: {
|
|
227
|
+
type: 'Boolean',
|
|
228
|
+
default: true
|
|
229
|
+
},
|
|
230
|
+
profile: {
|
|
231
|
+
bio: 'String',
|
|
232
|
+
avatar: 'String',
|
|
233
|
+
social: {
|
|
234
|
+
twitter: 'String',
|
|
235
|
+
github: 'String'
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
tags: ['String'],
|
|
239
|
+
createdAt: 'Date',
|
|
240
|
+
updatedAt: 'Date'
|
|
241
|
+
}, {
|
|
242
|
+
timestamps: true, // Auto-add createdAt/updatedAt
|
|
243
|
+
collection: 'users', // Collection name
|
|
244
|
+
versionKey: '__v', // Version key field
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Schema Types
|
|
249
|
+
|
|
250
|
+
All Mongoose schema types are supported:
|
|
251
|
+
|
|
252
|
+
- `String` - String values
|
|
253
|
+
- `Number` - Numeric values
|
|
254
|
+
- `Boolean` - true/false
|
|
255
|
+
- `Date` - Date objects
|
|
256
|
+
- `ObjectId` - MongoDB ObjectId
|
|
257
|
+
- `Array` - Arrays
|
|
258
|
+
- `Object` / `Mixed` - Any object
|
|
259
|
+
- `Buffer` - Binary data
|
|
260
|
+
|
|
261
|
+
### Schema Options
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const schema = new Schema(definition, {
|
|
265
|
+
timestamps: true, // Add createdAt/updatedAt
|
|
266
|
+
timestamps: { createdAt: 'created', updatedAt: 'modified' }, // Custom field names
|
|
267
|
+
collection: 'myCollection', // Collection name
|
|
268
|
+
strict: true, // Only save schema fields
|
|
269
|
+
versionKey: '__v', // Version key
|
|
270
|
+
_id: true, // Add _id field
|
|
271
|
+
autoIndex: true, // Auto-create indexes
|
|
272
|
+
minimize: true, // Remove empty objects
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Instance Methods
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Define instance methods
|
|
280
|
+
userSchema.methods.comparePassword = async function(password: string) {
|
|
281
|
+
return bcrypt.compare(password, this.password);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
userSchema.methods.generateToken = function() {
|
|
285
|
+
return jwt.sign({ id: this._id }, SECRET);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// Or use the method() function
|
|
289
|
+
userSchema.method('fullName', function() {
|
|
290
|
+
return `${this.firstName} ${this.lastName}`;
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Static Methods
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Define static methods
|
|
298
|
+
userSchema.statics.findByEmail = function(email: string) {
|
|
299
|
+
return this.findOne({ email: email.toLowerCase() });
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
userSchema.statics.findActive = function() {
|
|
303
|
+
return this.find({ isActive: true });
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Or use the static() function
|
|
307
|
+
userSchema.static('findByRole', function(role: string) {
|
|
308
|
+
return this.find({ role });
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Virtuals
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
userSchema.virtual('fullName')
|
|
316
|
+
.get(function() {
|
|
317
|
+
return `${this.firstName} ${this.lastName}`;
|
|
318
|
+
})
|
|
319
|
+
.set(function(name: string) {
|
|
320
|
+
const [firstName, lastName] = name.split(' ');
|
|
321
|
+
this.firstName = firstName;
|
|
322
|
+
this.lastName = lastName;
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Middleware (Hooks)
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// Pre-save hook
|
|
330
|
+
userSchema.pre('save', async function(next) {
|
|
331
|
+
if (this.isModified('password')) {
|
|
332
|
+
this.password = await bcrypt.hash(this.password, 10);
|
|
333
|
+
}
|
|
334
|
+
next();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Post-save hook
|
|
338
|
+
userSchema.post('save', function(next) {
|
|
339
|
+
console.log('User saved:', this._id);
|
|
340
|
+
next();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Query middleware
|
|
344
|
+
userSchema.pre('find', function(next) {
|
|
345
|
+
// Add filter to all find queries
|
|
346
|
+
this.where({ isDeleted: { $ne: true } });
|
|
347
|
+
next();
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Creating a Model
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { model } from 'harbor';
|
|
355
|
+
|
|
356
|
+
// Create model
|
|
357
|
+
const User = model('User', userSchema);
|
|
358
|
+
|
|
359
|
+
// With custom collection name
|
|
360
|
+
const User = model('User', userSchema, 'my_users');
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Query Methods
|
|
364
|
+
|
|
365
|
+
All Mongoose query methods are available:
|
|
366
|
+
|
|
367
|
+
### Find Documents
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Find all
|
|
371
|
+
const users = await User.find();
|
|
372
|
+
|
|
373
|
+
// Find with filter
|
|
374
|
+
const admins = await User.find({ role: 'admin' });
|
|
375
|
+
|
|
376
|
+
// Find one
|
|
377
|
+
const user = await User.findOne({ email: 'john@example.com' });
|
|
378
|
+
|
|
379
|
+
// Find by ID
|
|
380
|
+
const user = await User.findById('507f1f77bcf86cd799439011');
|
|
381
|
+
|
|
382
|
+
// Query builder
|
|
383
|
+
const users = await User.find()
|
|
384
|
+
.where('age').gte(18).lte(65)
|
|
385
|
+
.where('role').in(['user', 'admin'])
|
|
386
|
+
.select('name email role')
|
|
387
|
+
.sort('-createdAt')
|
|
388
|
+
.skip(0)
|
|
389
|
+
.limit(10)
|
|
390
|
+
.lean();
|
|
391
|
+
|
|
392
|
+
// Chain methods
|
|
393
|
+
const users = await User.find({ isActive: true })
|
|
394
|
+
.select('name email')
|
|
395
|
+
.sort({ createdAt: -1 })
|
|
396
|
+
.limit(10);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Create Documents
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// Create single document
|
|
403
|
+
const user = await User.create({
|
|
404
|
+
email: 'john@example.com',
|
|
405
|
+
name: 'John Doe',
|
|
406
|
+
password: 'secret123'
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Create multiple documents
|
|
410
|
+
const users = await User.create([
|
|
411
|
+
{ email: 'user1@example.com', name: 'User 1' },
|
|
412
|
+
{ email: 'user2@example.com', name: 'User 2' }
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
// Insert many
|
|
416
|
+
const result = await User.insertMany([
|
|
417
|
+
{ email: 'user1@example.com', name: 'User 1' },
|
|
418
|
+
{ email: 'user2@example.com', name: 'User 2' }
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
// Using new + save
|
|
422
|
+
const user = User.new({ email: 'john@example.com', name: 'John' });
|
|
423
|
+
await user.save();
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Update Documents
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// Update one
|
|
430
|
+
await User.updateOne(
|
|
431
|
+
{ email: 'john@example.com' },
|
|
432
|
+
{ $set: { name: 'John Smith' } }
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
// Update many
|
|
436
|
+
await User.updateMany(
|
|
437
|
+
{ role: 'user' },
|
|
438
|
+
{ $set: { isActive: true } }
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Find and update (returns document)
|
|
442
|
+
const user = await User.findOneAndUpdate(
|
|
443
|
+
{ email: 'john@example.com' },
|
|
444
|
+
{ $set: { name: 'John Smith' } },
|
|
445
|
+
{ new: true } // Return updated document
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Find by ID and update
|
|
449
|
+
const user = await User.findByIdAndUpdate(
|
|
450
|
+
'507f1f77bcf86cd799439011',
|
|
451
|
+
{ name: 'New Name' },
|
|
452
|
+
{ new: true }
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// Replace one
|
|
456
|
+
await User.replaceOne(
|
|
457
|
+
{ email: 'john@example.com' },
|
|
458
|
+
{ email: 'john@example.com', name: 'Replaced User' }
|
|
459
|
+
);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Delete Documents
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// Delete one
|
|
466
|
+
await User.deleteOne({ email: 'john@example.com' });
|
|
467
|
+
|
|
468
|
+
// Delete many
|
|
469
|
+
await User.deleteMany({ isActive: false });
|
|
470
|
+
|
|
471
|
+
// Find and delete
|
|
472
|
+
const deletedUser = await User.findOneAndDelete({ email: 'john@example.com' });
|
|
473
|
+
|
|
474
|
+
// Find by ID and delete
|
|
475
|
+
const deletedUser = await User.findByIdAndDelete('507f1f77bcf86cd799439011');
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Count & Exists
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// Count documents
|
|
482
|
+
const count = await User.countDocuments({ role: 'admin' });
|
|
483
|
+
|
|
484
|
+
// Estimated count (faster, but approximate)
|
|
485
|
+
const estimated = await User.estimatedDocumentCount();
|
|
486
|
+
|
|
487
|
+
// Check if exists
|
|
488
|
+
const exists = await User.exists({ email: 'john@example.com' });
|
|
489
|
+
// Returns { _id: '...' } or null
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Aggregation
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
const result = await User.aggregate([
|
|
496
|
+
{ $match: { isActive: true } },
|
|
497
|
+
{ $group: { _id: '$role', count: { $sum: 1 } } },
|
|
498
|
+
{ $sort: { count: -1 } }
|
|
499
|
+
]);
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Distinct
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
const roles = await User.distinct('role');
|
|
506
|
+
// ['user', 'admin', 'moderator']
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Indexes
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
// Create index
|
|
513
|
+
await User.createIndex({ email: 1 }, { unique: true });
|
|
514
|
+
|
|
515
|
+
// Create compound index
|
|
516
|
+
await User.createIndex({ name: 1, createdAt: -1 });
|
|
517
|
+
|
|
518
|
+
// List indexes
|
|
519
|
+
const indexes = await User.listIndexes();
|
|
520
|
+
|
|
521
|
+
// Drop index
|
|
522
|
+
await User.dropIndex('email_1');
|
|
523
|
+
|
|
524
|
+
// Define index in schema
|
|
525
|
+
userSchema.index({ email: 1 }, { unique: true });
|
|
526
|
+
userSchema.index({ name: 'text' }); // Text index
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Route Management
|
|
530
|
+
|
|
531
|
+
### Simple Routes
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
import { GET, POST, PUT, PATCH, DELETE } from 'harbor';
|
|
535
|
+
|
|
536
|
+
// GET request
|
|
537
|
+
const getUsers = GET('/api/users', async (req) => {
|
|
538
|
+
const users = await User.find();
|
|
539
|
+
return { users };
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// POST request with validation
|
|
543
|
+
const createUser = POST('/api/users', async (req) => {
|
|
544
|
+
const user = await User.create(req.validated.body);
|
|
545
|
+
return { user };
|
|
546
|
+
}, {
|
|
547
|
+
validation: {
|
|
548
|
+
body: {
|
|
549
|
+
email: { type: 'email', required: true },
|
|
550
|
+
name: { type: 'string', required: true }
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// PUT request
|
|
556
|
+
const updateUser = PUT('/api/users/:id', async (req) => {
|
|
557
|
+
const user = await User.findByIdAndUpdate(
|
|
558
|
+
req.validated.params.id,
|
|
559
|
+
req.validated.body,
|
|
560
|
+
{ new: true }
|
|
561
|
+
);
|
|
562
|
+
return { user };
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// DELETE request
|
|
566
|
+
const deleteUser = DELETE('/api/users/:id', async (req) => {
|
|
567
|
+
await User.findByIdAndDelete(req.validated.params.id);
|
|
568
|
+
return { deleted: true };
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Add routes to server
|
|
572
|
+
server.addRoute(getUsers);
|
|
573
|
+
server.addRoute(createUser);
|
|
574
|
+
server.addRoute(updateUser);
|
|
575
|
+
server.addRoute(deleteUser);
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Route Options
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
const route = POST('/api/users', handler, {
|
|
582
|
+
// Validation
|
|
583
|
+
validation: {
|
|
584
|
+
body: { /* schema */ },
|
|
585
|
+
params: { /* schema */ },
|
|
586
|
+
query: { /* schema */ },
|
|
587
|
+
headers: { /* schema */ }
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
// Middleware
|
|
591
|
+
pre: [authMiddleware, rateLimitMiddleware],
|
|
592
|
+
post: [logMiddleware],
|
|
593
|
+
|
|
594
|
+
// Timeout
|
|
595
|
+
timeout: 30000, // 30 seconds
|
|
596
|
+
|
|
597
|
+
// Metadata
|
|
598
|
+
tags: ['users'],
|
|
599
|
+
description: 'Create a new user'
|
|
600
|
+
});
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### Route Groups
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
// Group routes by prefix
|
|
607
|
+
const userRoutes = [
|
|
608
|
+
GET('/api/users', listUsers),
|
|
609
|
+
POST('/api/users', createUser),
|
|
610
|
+
GET('/api/users/:id', getUser),
|
|
611
|
+
PUT('/api/users/:id', updateUser),
|
|
612
|
+
DELETE('/api/users/:id', deleteUser),
|
|
613
|
+
];
|
|
614
|
+
|
|
615
|
+
server.addRoutes(userRoutes);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## Validation
|
|
619
|
+
|
|
620
|
+
### Request Validation
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
const route = POST('/api/users', handler, {
|
|
624
|
+
validation: {
|
|
625
|
+
body: {
|
|
626
|
+
email: { type: 'email', required: true },
|
|
627
|
+
password: { type: 'string', required: true, min: 8, max: 100 },
|
|
628
|
+
age: { type: 'number', min: 0, max: 150 },
|
|
629
|
+
role: { type: 'string', enum: ['user', 'admin'] },
|
|
630
|
+
website: { type: 'url' },
|
|
631
|
+
phone: { type: 'string', match: /^\+?[\d\s-]+$/ }
|
|
632
|
+
},
|
|
633
|
+
params: {
|
|
634
|
+
id: { type: 'objectId', required: true }
|
|
635
|
+
},
|
|
636
|
+
query: {
|
|
637
|
+
page: { type: 'number', default: 1 },
|
|
638
|
+
limit: { type: 'number', default: 20, max: 100 }
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Validation Types
|
|
645
|
+
|
|
646
|
+
- `string` - String values
|
|
647
|
+
- `number` - Numeric values
|
|
648
|
+
- `boolean` - true/false
|
|
649
|
+
- `email` - Valid email address
|
|
650
|
+
- `url` - Valid URL
|
|
651
|
+
- `objectId` - MongoDB ObjectId
|
|
652
|
+
- `date` - Valid date
|
|
653
|
+
- `array` - Array values
|
|
654
|
+
- `object` - Object values
|
|
655
|
+
|
|
656
|
+
### Custom Validators
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
const schema = {
|
|
660
|
+
password: {
|
|
661
|
+
type: 'string',
|
|
662
|
+
required: true,
|
|
663
|
+
validate: {
|
|
664
|
+
validator: (value) => /[A-Z]/.test(value) && /[0-9]/.test(value),
|
|
665
|
+
message: 'Password must contain uppercase letter and number'
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## Error Handling
|
|
672
|
+
|
|
673
|
+
### HarborError Class
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
import { HarborError } from 'harbor';
|
|
677
|
+
|
|
678
|
+
// Throw errors in your routes
|
|
679
|
+
const getUser = GET('/api/users/:id', async (req) => {
|
|
680
|
+
const user = await User.findById(req.params.id);
|
|
681
|
+
|
|
682
|
+
if (!user) {
|
|
683
|
+
throw HarborError.notFound('User not found');
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (!user.isActive) {
|
|
687
|
+
throw HarborError.forbidden('User account is disabled');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return { user };
|
|
691
|
+
});
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Available Error Methods
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
HarborError.badRequest(message?, details?) // 400
|
|
698
|
+
HarborError.unauthorized(message?) // 401
|
|
699
|
+
HarborError.forbidden(message?) // 403
|
|
700
|
+
HarborError.notFound(message?) // 404
|
|
701
|
+
HarborError.conflict(message?, details?) // 409
|
|
702
|
+
HarborError.tooManyRequests(message?) // 429
|
|
703
|
+
HarborError.internal(message?) // 500
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Error Configuration
|
|
707
|
+
|
|
708
|
+
Configure error responses in `harbor.config.json`:
|
|
709
|
+
|
|
710
|
+
```json
|
|
711
|
+
{
|
|
712
|
+
"errors": {
|
|
713
|
+
"401": {
|
|
714
|
+
"message": "Please log in to continue",
|
|
715
|
+
"json": true,
|
|
716
|
+
"log": true
|
|
717
|
+
},
|
|
718
|
+
"404": {
|
|
719
|
+
"message": "Resource not found",
|
|
720
|
+
"json": true
|
|
721
|
+
},
|
|
722
|
+
"500": {
|
|
723
|
+
"message": "Something went wrong",
|
|
724
|
+
"json": true,
|
|
725
|
+
"log": true
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
## Middleware
|
|
732
|
+
|
|
733
|
+
### HTTP Logger (Morgan-like)
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
import { httpLogger } from 'harbor';
|
|
737
|
+
|
|
738
|
+
// Add logging middleware
|
|
739
|
+
server.addMiddleware(httpLogger({ format: 'dev' }));
|
|
740
|
+
|
|
741
|
+
// Available formats: 'tiny', 'short', 'dev', 'combined', 'common'
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Custom Format
|
|
745
|
+
|
|
746
|
+
```typescript
|
|
747
|
+
server.addMiddleware(httpLogger({
|
|
748
|
+
format: 'custom',
|
|
749
|
+
customFormat: (tokens) =>
|
|
750
|
+
`${tokens.method} ${tokens.url} ${tokens.status} - ${tokens.responseTime}ms`
|
|
751
|
+
}));
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Skip Requests
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
import { httpLogger, skipFunctions } from 'harbor';
|
|
758
|
+
|
|
759
|
+
server.addMiddleware(httpLogger({
|
|
760
|
+
format: 'dev',
|
|
761
|
+
skip: skipFunctions.healthChecks // Skip /health and /healthz
|
|
762
|
+
}));
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Pre/Post Route Middleware
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
const authMiddleware = async (req, res, next) => {
|
|
769
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
770
|
+
if (!token) {
|
|
771
|
+
throw HarborError.unauthorized('No token provided');
|
|
772
|
+
}
|
|
773
|
+
req.user = await verifyToken(token);
|
|
774
|
+
next();
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const logMiddleware = async (req, res, result, next) => {
|
|
778
|
+
console.log('Response:', result);
|
|
779
|
+
next();
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
const route = GET('/api/profile', handler, {
|
|
783
|
+
pre: [authMiddleware],
|
|
784
|
+
post: [logMiddleware]
|
|
785
|
+
});
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
## Docker Manager
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
import { createDockerManager } from 'harbor';
|
|
792
|
+
|
|
793
|
+
const docker = createDockerManager({
|
|
794
|
+
imageName: 'my-app',
|
|
795
|
+
tag: 'latest',
|
|
796
|
+
dockerfile: './Dockerfile',
|
|
797
|
+
context: '.'
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// Build image
|
|
801
|
+
await docker.build();
|
|
802
|
+
|
|
803
|
+
// Push to registry
|
|
804
|
+
await docker.push('registry.example.com');
|
|
805
|
+
|
|
806
|
+
// Pull image
|
|
807
|
+
await docker.pull('nginx:latest');
|
|
808
|
+
|
|
809
|
+
// Container operations
|
|
810
|
+
await docker.startContainer('my-container');
|
|
811
|
+
await docker.stopContainer('my-container');
|
|
812
|
+
await docker.restartContainer('my-container');
|
|
813
|
+
|
|
814
|
+
// Docker Compose
|
|
815
|
+
await docker.composeUp();
|
|
816
|
+
await docker.composeDown();
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## Configuration
|
|
820
|
+
|
|
821
|
+
Create `harbor.config.json` in your project root:
|
|
822
|
+
|
|
823
|
+
```json
|
|
824
|
+
{
|
|
825
|
+
"server": {
|
|
826
|
+
"port": 3000,
|
|
827
|
+
"host": "localhost"
|
|
828
|
+
},
|
|
829
|
+
"cors": {
|
|
830
|
+
"enabled": true,
|
|
831
|
+
"origin": "*",
|
|
832
|
+
"methods": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
833
|
+
"allowedHeaders": ["Content-Type", "Authorization"]
|
|
834
|
+
},
|
|
835
|
+
"logger": {
|
|
836
|
+
"level": "info",
|
|
837
|
+
"format": "dev"
|
|
838
|
+
},
|
|
839
|
+
"validation": {
|
|
840
|
+
"abortEarly": false,
|
|
841
|
+
"stripUnknown": true
|
|
842
|
+
},
|
|
843
|
+
"errors": {
|
|
844
|
+
"404": { "message": "Not Found", "json": true },
|
|
845
|
+
"500": { "message": "Internal Error", "json": true, "log": true }
|
|
846
|
+
},
|
|
847
|
+
"docker": {
|
|
848
|
+
"imageName": "my-app",
|
|
849
|
+
"tag": "latest"
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
## i18n (Translations)
|
|
855
|
+
|
|
856
|
+
```typescript
|
|
857
|
+
import { setLocale, t, addTranslations } from 'harbor';
|
|
858
|
+
|
|
859
|
+
// Set locale
|
|
860
|
+
setLocale('he');
|
|
861
|
+
|
|
862
|
+
// Use translations
|
|
863
|
+
console.log(t('server.started', { host: 'localhost', port: 3000 }));
|
|
864
|
+
// Output (Hebrew): השרת פועל בכתובת http://localhost:3000
|
|
865
|
+
|
|
866
|
+
// Add custom translations
|
|
867
|
+
addTranslations('en', {
|
|
868
|
+
'custom.message': 'Hello, {name}!'
|
|
869
|
+
});
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
### Available Locales
|
|
873
|
+
|
|
874
|
+
- `en` - English (default)
|
|
875
|
+
- `he` - Hebrew
|
|
876
|
+
|
|
877
|
+
## API Reference
|
|
878
|
+
|
|
879
|
+
### Core Functions
|
|
880
|
+
|
|
881
|
+
| Function | Description |
|
|
882
|
+
|----------|-------------|
|
|
883
|
+
| `createServer(options)` | Create a Harbor server |
|
|
884
|
+
| `connect(uri, options)` | Connect to MongoDB |
|
|
885
|
+
| `disconnect()` | Disconnect from MongoDB |
|
|
886
|
+
| `Schema(definition, options)` | Create a schema |
|
|
887
|
+
| `model(name, schema)` | Create a model |
|
|
888
|
+
| `GET/POST/PUT/PATCH/DELETE(path, handler, options)` | Create routes |
|
|
889
|
+
|
|
890
|
+
### Model Methods
|
|
891
|
+
|
|
892
|
+
| Method | Description |
|
|
893
|
+
|--------|-------------|
|
|
894
|
+
| `find(filter)` | Find documents |
|
|
895
|
+
| `findOne(filter)` | Find one document |
|
|
896
|
+
| `findById(id)` | Find by ID |
|
|
897
|
+
| `create(doc)` | Create document(s) |
|
|
898
|
+
| `updateOne(filter, update)` | Update one |
|
|
899
|
+
| `updateMany(filter, update)` | Update many |
|
|
900
|
+
| `deleteOne(filter)` | Delete one |
|
|
901
|
+
| `deleteMany(filter)` | Delete many |
|
|
902
|
+
| `findOneAndUpdate(filter, update, options)` | Find and update |
|
|
903
|
+
| `findOneAndDelete(filter)` | Find and delete |
|
|
904
|
+
| `countDocuments(filter)` | Count documents |
|
|
905
|
+
| `aggregate(pipeline)` | Aggregation pipeline |
|
|
906
|
+
|
|
907
|
+
### Query Methods
|
|
908
|
+
|
|
909
|
+
| Method | Description |
|
|
910
|
+
|--------|-------------|
|
|
911
|
+
| `.select(fields)` | Select fields |
|
|
912
|
+
| `.sort(fields)` | Sort results |
|
|
913
|
+
| `.limit(n)` | Limit results |
|
|
914
|
+
| `.skip(n)` | Skip results |
|
|
915
|
+
| `.lean()` | Return plain objects |
|
|
916
|
+
| `.where(path)` | Add condition |
|
|
917
|
+
| `.gt/gte/lt/lte(value)` | Comparison operators |
|
|
918
|
+
| `.in/nin(values)` | Array operators |
|
|
919
|
+
| `.regex(pattern)` | Regex match |
|
|
920
|
+
|
|
921
|
+
## License
|
|
922
|
+
|
|
923
|
+
MIT © Harbor Contributors
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
**Happy Sailing! 🚢**
|