@forgedevstack/harbor 1.0.0 → 1.5.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 +82 -101
- package/README.md +210 -794
- package/dist/auth/apiKey.d.ts +6 -0
- package/dist/auth/apiKey.d.ts.map +1 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/jwt.d.ts +21 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/password.d.ts +6 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/rbac.d.ts +6 -0
- package/dist/auth/rbac.d.ts.map +1 -0
- package/dist/auth/signing.d.ts +5 -0
- package/dist/auth/signing.d.ts.map +1 -0
- package/dist/auth/types/apiKey.types.d.ts +9 -0
- package/dist/auth/types/apiKey.types.d.ts.map +1 -0
- package/dist/auth/types/index.d.ts +5 -0
- package/dist/auth/types/index.d.ts.map +1 -0
- package/dist/auth/types/jwt.types.d.ts +17 -0
- package/dist/auth/types/jwt.types.d.ts.map +1 -0
- package/dist/auth/types/rbac.types.d.ts +8 -0
- package/dist/auth/types/rbac.types.d.ts.map +1 -0
- package/dist/auth/types/signing.types.d.ts +8 -0
- package/dist/auth/types/signing.types.d.ts.map +1 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +2 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/manager.d.ts +24 -0
- package/dist/cache/manager.d.ts.map +1 -0
- package/dist/cache/stores.d.ts +28 -0
- package/dist/cache/stores.d.ts.map +1 -0
- package/dist/cache/types.d.ts +23 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cli/index.js +21 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/router.d.ts +40 -2
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/database/connection.d.ts +1 -2
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/index.js +2 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/model.d.ts +1 -4
- package/dist/database/model.d.ts.map +1 -1
- package/dist/docker/index.js +1 -1
- package/dist/http.const-BKHG1Lsj.mjs +62 -0
- package/dist/http.const-BKHG1Lsj.mjs.map +1 -0
- package/dist/http.const-Ckcy7OFp.js +2 -0
- package/dist/http.const-Ckcy7OFp.js.map +1 -0
- package/dist/index-Ca4WpLvw.js +2 -0
- package/dist/index-Ca4WpLvw.js.map +1 -0
- package/dist/index-DIVHd6rO.mjs +1054 -0
- package/dist/index-DIVHd6rO.mjs.map +1 -0
- package/dist/index.cjs.js +16 -16
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +676 -1691
- package/dist/index.es.js.map +1 -1
- package/dist/logger-CZn7QxCl.mjs +102 -0
- package/dist/{logger-D7aJSi62.mjs.map → logger-CZn7QxCl.mjs.map} +1 -1
- package/dist/logger-D-lfaRWQ.js +3 -0
- package/dist/{logger-DEnWXtpk.js.map → logger-D-lfaRWQ.js.map} +1 -1
- package/dist/manager-CjcKb4P9.mjs +149 -0
- package/dist/{manager-B6vqJgEn.mjs.map → manager-CjcKb4P9.mjs.map} +1 -1
- package/dist/manager-DrF1vbJg.js +4 -0
- package/dist/{manager-B1UKMjXW.js.map → manager-DrF1vbJg.js.map} +1 -1
- package/dist/middleware/health.d.ts +65 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/metrics.d.ts +68 -0
- package/dist/middleware/metrics.d.ts.map +1 -0
- package/dist/middleware/rateLimit.d.ts +52 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/upload.d.ts +59 -0
- package/dist/middleware/upload.d.ts.map +1 -0
- package/dist/password-BXBkKbv3.js +2 -0
- package/dist/password-BXBkKbv3.js.map +1 -0
- package/dist/password-y4m307oa.mjs +223 -0
- package/dist/password-y4m307oa.mjs.map +1 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +30 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/types.d.ts +25 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/dist/types/server.types.d.ts +7 -0
- package/dist/types/server.types.d.ts.map +1 -1
- package/dist/upload-9lCNnKK_.js +5 -0
- package/dist/upload-9lCNnKK_.js.map +1 -0
- package/dist/upload-DUjQiuq7.mjs +619 -0
- package/dist/upload-DUjQiuq7.mjs.map +1 -0
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +2 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/manager.d.ts +30 -0
- package/dist/websocket/manager.d.ts.map +1 -0
- package/dist/websocket/types.d.ts +27 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/package.json +58 -18
- package/templates/default/controllers/user.controller.ts +44 -64
- package/templates/default/package.json +9 -33
- package/templates/default/routes/index.ts +2 -12
- package/templates/default/routes/user.routes.ts +26 -19
- package/templates/default/server.ts +16 -35
- package/dist/logger-D7aJSi62.mjs +0 -102
- package/dist/logger-DEnWXtpk.js +0 -3
- package/dist/manager-B1UKMjXW.js +0 -4
- package/dist/manager-B6vqJgEn.mjs +0 -152
- package/dist/portal.d.ts +0 -13
- package/dist/portal.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,927 +1,343 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @forgestack/harbor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://forgestack.dev/harbor-logo.svg" alt="Harbor Logo" width="120" />
|
|
5
|
+
</p>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Complete Node.js backend framework</strong><br/>
|
|
9
|
+
MongoDB ODM • WebSocket • Scheduling • Caching • Auth • Metrics
|
|
10
|
+
</p>
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/@forgestack/harbor"><img src="https://img.shields.io/npm/v/@forgestack/harbor.svg" alt="npm"></a>
|
|
14
|
+
<a href="https://github.com/yaghobieh/ForgeStack"><img src="https://img.shields.io/github/stars/yaghobieh/ForgeStack.svg" alt="stars"></a>
|
|
15
|
+
<a href="https://github.com/yaghobieh/ForgeStack/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@forgestack/harbor.svg" alt="license"></a>
|
|
16
|
+
</p>
|
|
10
17
|
|
|
11
|
-
|
|
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)
|
|
18
|
+
---
|
|
27
19
|
|
|
28
20
|
## Features
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
| Feature | Description |
|
|
23
|
+
|---------|-------------|
|
|
24
|
+
| **Zero-Config Server** | Create servers in seconds with Express under the hood |
|
|
25
|
+
| **MongoDB ODM** | Full Mongoose replacement with Schema, Model, Query |
|
|
26
|
+
| **Authentication** | JWT, API Key, RBAC, request signing |
|
|
27
|
+
| **WebSocket** | Real-time with rooms and broadcasting |
|
|
28
|
+
| **Scheduler** | Cron expressions and interval-based jobs |
|
|
29
|
+
| **Rate Limiting** | Memory and Redis stores |
|
|
30
|
+
| **Caching** | Memory and Redis with middleware |
|
|
31
|
+
| **Metrics** | Prometheus-compatible endpoint |
|
|
32
|
+
| **Health Checks** | MongoDB, Redis, Memory, Disk checks |
|
|
33
|
+
| **File Uploads** | Multipart parsing with validation |
|
|
34
|
+
| **i18n** | Built-in translation system |
|
|
39
35
|
|
|
40
36
|
## Installation
|
|
41
37
|
|
|
42
38
|
```bash
|
|
43
|
-
|
|
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
|
|
39
|
+
npm install @forgestack/harbor
|
|
59
40
|
```
|
|
60
41
|
|
|
61
42
|
## Quick Start
|
|
62
43
|
|
|
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
44
|
```typescript
|
|
76
|
-
import { createServer,
|
|
45
|
+
import { createServer, router, route } from '@forgestack/harbor';
|
|
77
46
|
|
|
78
|
-
// Connect to MongoDB
|
|
79
|
-
await connect('mongodb://localhost:27017/myapp');
|
|
80
|
-
|
|
81
|
-
// Create server
|
|
82
47
|
const server = createServer({ port: 3000 });
|
|
83
48
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
49
|
+
const users = router('/api/users', [
|
|
50
|
+
route.get('/', async () => ({ users: [] })),
|
|
51
|
+
route.post('/', async (req) => ({ id: '123', ...req.body })),
|
|
52
|
+
route.delete('/:id', async (req) => ({ deleted: req.params.id })),
|
|
53
|
+
]);
|
|
107
54
|
|
|
108
|
-
|
|
109
|
-
|
|
55
|
+
server.use(users);
|
|
56
|
+
server.listen(3000, () => console.log('Server running!'));
|
|
110
57
|
```
|
|
111
58
|
|
|
112
|
-
##
|
|
59
|
+
## MongoDB ODM
|
|
113
60
|
|
|
114
|
-
|
|
61
|
+
Full Mongoose replacement:
|
|
115
62
|
|
|
116
63
|
```typescript
|
|
117
|
-
import {
|
|
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
|
|
64
|
+
import { Schema, model, connect } from '@forgestack/harbor/database';
|
|
130
65
|
|
|
131
|
-
|
|
132
|
-
// Add routes
|
|
133
|
-
server.addRoute(route);
|
|
134
|
-
server.addRoutes([route1, route2]);
|
|
66
|
+
await connect('mongodb://localhost:27017/myapp');
|
|
135
67
|
|
|
136
|
-
|
|
137
|
-
|
|
68
|
+
const UserSchema = new Schema({
|
|
69
|
+
email: { type: 'string', required: true, unique: true },
|
|
70
|
+
name: { type: 'string', required: true },
|
|
71
|
+
role: { type: 'string', enum: ['user', 'admin'], default: 'user' },
|
|
72
|
+
createdAt: { type: 'date', default: () => new Date() },
|
|
73
|
+
});
|
|
138
74
|
|
|
139
|
-
|
|
140
|
-
const app = server.getApp();
|
|
75
|
+
const User = model('User', UserSchema);
|
|
141
76
|
|
|
142
|
-
//
|
|
143
|
-
await
|
|
77
|
+
// All Mongoose-like methods
|
|
78
|
+
const user = await User.create({ email: 'john@example.com', name: 'John' });
|
|
79
|
+
const admins = await User.find({ role: 'admin' });
|
|
80
|
+
const found = await User.findOne({ email: 'john@example.com' });
|
|
81
|
+
await User.updateOne({ _id: user._id }, { role: 'admin' });
|
|
82
|
+
await User.deleteOne({ _id: user._id });
|
|
144
83
|
```
|
|
145
84
|
|
|
146
|
-
##
|
|
85
|
+
## Authentication
|
|
147
86
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
### Connecting to MongoDB
|
|
87
|
+
### JWT
|
|
151
88
|
|
|
152
89
|
```typescript
|
|
153
|
-
import {
|
|
90
|
+
import { JWT, jwtAuth, requireRole } from '@forgestack/harbor';
|
|
154
91
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
serverSelectionTimeoutMS: 30000,
|
|
92
|
+
const jwt = new JWT({
|
|
93
|
+
secret: process.env.JWT_SECRET!,
|
|
94
|
+
expiresIn: 3600, // 1 hour
|
|
159
95
|
});
|
|
160
96
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
connection.on('disconnected', () => console.log('Disconnected!'));
|
|
164
|
-
connection.on('error', (err) => console.error('Error:', err));
|
|
97
|
+
// Generate token
|
|
98
|
+
const token = jwt.sign({ userId: '123', role: 'admin' });
|
|
165
99
|
|
|
166
|
-
//
|
|
167
|
-
|
|
100
|
+
// Verify token
|
|
101
|
+
const payload = jwt.verify(token);
|
|
168
102
|
|
|
169
|
-
//
|
|
170
|
-
|
|
103
|
+
// Protect routes
|
|
104
|
+
app.use('/api', jwtAuth(jwt));
|
|
105
|
+
app.get('/api/admin', requireRole('admin'), adminHandler);
|
|
171
106
|
```
|
|
172
107
|
|
|
173
|
-
###
|
|
108
|
+
### API Key
|
|
174
109
|
|
|
175
110
|
```typescript
|
|
176
|
-
|
|
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
|
-
```
|
|
111
|
+
import { apiKeyAuth, generateApiKey } from '@forgestack/harbor';
|
|
189
112
|
|
|
190
|
-
|
|
113
|
+
const key = generateApiKey(); // Generate a secure API key
|
|
191
114
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
115
|
+
app.use(apiKeyAuth({
|
|
116
|
+
header: 'X-API-Key',
|
|
117
|
+
validator: async (key) => {
|
|
118
|
+
const apiKey = await db.apiKeys.findOne({ key, active: true });
|
|
119
|
+
return apiKey ? { valid: true, data: apiKey } : false;
|
|
210
120
|
},
|
|
211
|
-
|
|
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
|
-
});
|
|
121
|
+
}));
|
|
246
122
|
```
|
|
247
123
|
|
|
248
|
-
|
|
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
|
|
124
|
+
## WebSocket
|
|
262
125
|
|
|
263
126
|
```typescript
|
|
264
|
-
|
|
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
|
|
127
|
+
import { createWebSocketServer } from '@forgestack/harbor';
|
|
277
128
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return `${this.firstName} ${this.lastName}`;
|
|
129
|
+
const wss = createWebSocketServer({
|
|
130
|
+
path: '/ws',
|
|
131
|
+
onConnection: (client) => {
|
|
132
|
+
console.log('Client connected:', client.id);
|
|
133
|
+
wss.join(client, 'lobby');
|
|
134
|
+
},
|
|
135
|
+
onMessage: (client, data) => {
|
|
136
|
+
wss.broadcastToRoom('lobby', { user: client.id, message: data });
|
|
137
|
+
},
|
|
138
|
+
onClose: (client) => {
|
|
139
|
+
console.log('Client disconnected:', client.id);
|
|
140
|
+
},
|
|
291
141
|
});
|
|
292
|
-
```
|
|
293
142
|
|
|
294
|
-
|
|
143
|
+
// Attach to server
|
|
144
|
+
await wss.attach(server.server);
|
|
295
145
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
});
|
|
146
|
+
// Broadcasting
|
|
147
|
+
wss.broadcast({ type: 'announcement', text: 'Hello everyone!' });
|
|
148
|
+
wss.broadcastToRoom('lobby', { type: 'chat', text: 'Hello lobby!' });
|
|
149
|
+
wss.send(clientId, { type: 'private', text: 'Just for you' });
|
|
310
150
|
```
|
|
311
151
|
|
|
312
|
-
|
|
152
|
+
## Scheduler
|
|
313
153
|
|
|
314
154
|
```typescript
|
|
315
|
-
|
|
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)
|
|
155
|
+
import { createScheduler } from '@forgestack/harbor';
|
|
327
156
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
userSchema.pre('save', async function(next) {
|
|
331
|
-
if (this.isModified('password')) {
|
|
332
|
-
this.password = await bcrypt.hash(this.password, 10);
|
|
333
|
-
}
|
|
334
|
-
next();
|
|
157
|
+
const scheduler = createScheduler({
|
|
158
|
+
onJobComplete: (job, duration) => console.log(`${job.name} took ${duration}ms`),
|
|
335
159
|
});
|
|
336
160
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
next();
|
|
161
|
+
// Cron expression (daily at midnight)
|
|
162
|
+
scheduler.cron('cleanup', '0 0 * * *', async () => {
|
|
163
|
+
await db.logs.deleteMany({ createdAt: { $lt: thirtyDaysAgo } });
|
|
341
164
|
});
|
|
342
165
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.where({ isDeleted: { $ne: true } });
|
|
347
|
-
next();
|
|
166
|
+
// Every 5 minutes
|
|
167
|
+
scheduler.every('5m', 'healthCheck', async () => {
|
|
168
|
+
await pingServices();
|
|
348
169
|
});
|
|
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
170
|
|
|
359
|
-
//
|
|
360
|
-
|
|
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'
|
|
171
|
+
// One-time job
|
|
172
|
+
scheduler.at(new Date('2026-01-20'), 'reminder', async () => {
|
|
173
|
+
await sendReminder();
|
|
407
174
|
});
|
|
408
175
|
|
|
409
|
-
|
|
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();
|
|
176
|
+
scheduler.start();
|
|
424
177
|
```
|
|
425
178
|
|
|
426
|
-
|
|
179
|
+
## Rate Limiting
|
|
427
180
|
|
|
428
181
|
```typescript
|
|
429
|
-
|
|
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();
|
|
182
|
+
import { rateLimit, slidingWindowRateLimit, RedisStore } from '@forgestack/harbor';
|
|
486
183
|
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
184
|
+
// Memory store (default)
|
|
185
|
+
app.use(rateLimit({
|
|
186
|
+
max: 100,
|
|
187
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
188
|
+
}));
|
|
491
189
|
|
|
492
|
-
|
|
190
|
+
// Strict for login
|
|
191
|
+
app.post('/login', rateLimit({ max: 5, windowMs: 60000 }), loginHandler);
|
|
493
192
|
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
{ $group: { _id: '$role', count: { $sum: 1 } } },
|
|
498
|
-
{ $sort: { count: -1 } }
|
|
499
|
-
]);
|
|
193
|
+
// Redis store for distributed systems
|
|
194
|
+
const redisStore = new RedisStore(redisClient, 15 * 60 * 1000);
|
|
195
|
+
app.use(rateLimit({ store: redisStore }));
|
|
500
196
|
```
|
|
501
197
|
|
|
502
|
-
|
|
198
|
+
## Caching
|
|
503
199
|
|
|
504
200
|
```typescript
|
|
505
|
-
|
|
506
|
-
// ['user', 'admin', 'moderator']
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
### Indexes
|
|
201
|
+
import { cache, cacheResponse, createCache, RedisCache } from '@forgestack/harbor';
|
|
510
202
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
await
|
|
203
|
+
// Manual caching
|
|
204
|
+
const products = await cache.getOrSet('products', async () => {
|
|
205
|
+
return await db.products.find();
|
|
206
|
+
}, 60000); // 1 minute TTL
|
|
514
207
|
|
|
515
|
-
//
|
|
516
|
-
await
|
|
208
|
+
// Delete cache
|
|
209
|
+
await cache.del('products');
|
|
517
210
|
|
|
518
|
-
//
|
|
519
|
-
|
|
211
|
+
// Invalidate by pattern
|
|
212
|
+
await cache.invalidate('products:*');
|
|
520
213
|
|
|
521
|
-
//
|
|
522
|
-
|
|
214
|
+
// Middleware caching
|
|
215
|
+
app.get('/api/products', cacheResponse({ ttl: 60000 }), handler);
|
|
523
216
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
userSchema.index({ name: 'text' }); // Text index
|
|
217
|
+
// Redis cache
|
|
218
|
+
const redisCache = createCache(new RedisCache(redisClient), 3600000);
|
|
527
219
|
```
|
|
528
220
|
|
|
529
|
-
##
|
|
530
|
-
|
|
531
|
-
### Simple Routes
|
|
221
|
+
## Metrics & Health Checks
|
|
532
222
|
|
|
533
223
|
```typescript
|
|
534
|
-
import {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
});
|
|
224
|
+
import {
|
|
225
|
+
metricsMiddleware,
|
|
226
|
+
metricsEndpoint,
|
|
227
|
+
healthCheck,
|
|
228
|
+
mongoHealthCheck,
|
|
229
|
+
redisHealthCheck,
|
|
230
|
+
memoryHealthCheck,
|
|
231
|
+
} from '@forgestack/harbor';
|
|
570
232
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
server.addRoute(createUser);
|
|
574
|
-
server.addRoute(updateUser);
|
|
575
|
-
server.addRoute(deleteUser);
|
|
576
|
-
```
|
|
233
|
+
// Collect metrics
|
|
234
|
+
app.use(metricsMiddleware());
|
|
577
235
|
|
|
578
|
-
|
|
236
|
+
// Prometheus scrape endpoint
|
|
237
|
+
app.get('/metrics', metricsEndpoint());
|
|
579
238
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
});
|
|
239
|
+
// Health check endpoint
|
|
240
|
+
app.get('/health', healthCheck({
|
|
241
|
+
checks: [
|
|
242
|
+
mongoHealthCheck(db.connection),
|
|
243
|
+
redisHealthCheck(redisClient),
|
|
244
|
+
memoryHealthCheck(512), // Max 512MB heap
|
|
245
|
+
],
|
|
246
|
+
}));
|
|
601
247
|
```
|
|
602
248
|
|
|
603
|
-
|
|
249
|
+
## File Uploads
|
|
604
250
|
|
|
605
251
|
```typescript
|
|
606
|
-
|
|
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
|
-
```
|
|
252
|
+
import { upload } from '@forgestack/harbor';
|
|
617
253
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
}
|
|
254
|
+
app.post('/upload', upload({
|
|
255
|
+
dest: './uploads',
|
|
256
|
+
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
|
|
257
|
+
allowedMimeTypes: ['image/jpeg', 'image/png'],
|
|
258
|
+
}), (req, res) => {
|
|
259
|
+
console.log(req.file); // { path, originalName, mimeType, size }
|
|
260
|
+
res.json({ uploaded: true });
|
|
641
261
|
});
|
|
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
262
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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 };
|
|
263
|
+
// Multiple files
|
|
264
|
+
app.post('/gallery', upload({ limits: { files: 10 } }), (req, res) => {
|
|
265
|
+
console.log(req.files); // Array of uploaded files
|
|
691
266
|
});
|
|
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
267
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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]
|
|
268
|
+
// Memory storage (for processing)
|
|
269
|
+
app.post('/process', upload({ storage: 'memory' }), (req, res) => {
|
|
270
|
+
const buffer = req.file.buffer;
|
|
271
|
+
// Process buffer...
|
|
785
272
|
});
|
|
786
273
|
```
|
|
787
274
|
|
|
788
|
-
##
|
|
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();
|
|
275
|
+
## CLI
|
|
802
276
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
// Pull image
|
|
807
|
-
await docker.pull('nginx:latest');
|
|
277
|
+
```bash
|
|
278
|
+
# Initialize new project
|
|
279
|
+
npx @forgestack/harbor init my-api
|
|
808
280
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
await docker.stopContainer('my-container');
|
|
812
|
-
await docker.restartContainer('my-container');
|
|
281
|
+
# With template
|
|
282
|
+
npx @forgestack/harbor init my-api --template default
|
|
813
283
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
284
|
+
# Generate files
|
|
285
|
+
npx @forgestack/harbor generate model User
|
|
286
|
+
npx @forgestack/harbor generate controller User
|
|
287
|
+
npx @forgestack/harbor generate route users
|
|
817
288
|
```
|
|
818
289
|
|
|
819
290
|
## Configuration
|
|
820
291
|
|
|
821
|
-
Create `harbor.config.json
|
|
292
|
+
Create `harbor.config.json`:
|
|
822
293
|
|
|
823
294
|
```json
|
|
824
295
|
{
|
|
825
296
|
"server": {
|
|
826
297
|
"port": 3000,
|
|
827
|
-
"host": "localhost"
|
|
298
|
+
"host": "localhost",
|
|
299
|
+
"cors": {
|
|
300
|
+
"enabled": true,
|
|
301
|
+
"origin": "*"
|
|
302
|
+
}
|
|
828
303
|
},
|
|
829
|
-
"
|
|
830
|
-
"
|
|
831
|
-
"origin": "*",
|
|
832
|
-
"methods": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
833
|
-
"allowedHeaders": ["Content-Type", "Authorization"]
|
|
304
|
+
"database": {
|
|
305
|
+
"uri": "mongodb://localhost:27017/myapp"
|
|
834
306
|
},
|
|
835
307
|
"logger": {
|
|
836
|
-
"level": "info"
|
|
837
|
-
"format": "dev"
|
|
838
|
-
},
|
|
839
|
-
"validation": {
|
|
840
|
-
"abortEarly": false,
|
|
841
|
-
"stripUnknown": true
|
|
308
|
+
"level": "info"
|
|
842
309
|
},
|
|
843
310
|
"errors": {
|
|
844
|
-
"
|
|
845
|
-
"500": { "message": "Internal Error", "json": true, "log": true }
|
|
846
|
-
},
|
|
847
|
-
"docker": {
|
|
848
|
-
"imageName": "my-app",
|
|
849
|
-
"tag": "latest"
|
|
311
|
+
"showStack": false
|
|
850
312
|
}
|
|
851
313
|
}
|
|
852
314
|
```
|
|
853
315
|
|
|
854
|
-
##
|
|
316
|
+
## Subpath Imports
|
|
855
317
|
|
|
856
318
|
```typescript
|
|
857
|
-
|
|
319
|
+
// Core
|
|
320
|
+
import { createServer, router, route } from '@forgestack/harbor';
|
|
858
321
|
|
|
859
|
-
//
|
|
860
|
-
|
|
322
|
+
// Database
|
|
323
|
+
import { Schema, model, connect } from '@forgestack/harbor/database';
|
|
861
324
|
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
// Output (Hebrew): השרת פועל בכתובת http://localhost:3000
|
|
325
|
+
// Middleware
|
|
326
|
+
import { rateLimit, healthCheck, upload } from '@forgestack/harbor/middleware';
|
|
865
327
|
|
|
866
|
-
//
|
|
867
|
-
|
|
868
|
-
'custom.message': 'Hello, {name}!'
|
|
869
|
-
});
|
|
870
|
-
```
|
|
328
|
+
// Auth
|
|
329
|
+
import { JWT, jwtAuth, apiKeyAuth } from '@forgestack/harbor/auth';
|
|
871
330
|
|
|
872
|
-
|
|
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 |
|
|
331
|
+
// Cache
|
|
332
|
+
import { cache, cacheResponse } from '@forgestack/harbor/cache';
|
|
920
333
|
|
|
921
|
-
|
|
334
|
+
// Scheduler
|
|
335
|
+
import { createScheduler } from '@forgestack/harbor/scheduler';
|
|
922
336
|
|
|
923
|
-
|
|
337
|
+
// WebSocket
|
|
338
|
+
import { createWebSocketServer } from '@forgestack/harbor/websocket';
|
|
339
|
+
```
|
|
924
340
|
|
|
925
|
-
|
|
341
|
+
## License
|
|
926
342
|
|
|
927
|
-
|
|
343
|
+
MIT © [John Yaghobieh](https://github.com/yaghobieh)
|