@paralect/hive 0.1.49 → 0.1.50-alpha.2
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/.hive/.babelrc +3 -0
- package/.hive/.cursor/commands/add-endpoint.md +262 -0
- package/.hive/.cursor/commands/add-handler.md +137 -0
- package/.hive/.cursor/commands/add-middleware.md +95 -0
- package/.hive/.cursor/commands/add-resource.md +71 -0
- package/.hive/.cursor/commands/add-scheduler.md +138 -0
- package/.hive/.cursor/commands/add-service.md +188 -0
- package/.hive/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/.hive/.cursor/skills/hive-database/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/.hive/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/.hive/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/.hive/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/.hive/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/.hive/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/.hive/.cursor/skills/hive-service/SKILL.md +90 -0
- package/.hive/.dockerignore +1 -0
- package/.hive/Dockerfile +22 -0
- package/.hive/Dockerfile.dev +33 -0
- package/.hive/Dockerfile.prod +29 -0
- package/.hive/README.md +11 -0
- package/.hive/bin/deploy.sh +5 -0
- package/.hive/bin/start.sh +2 -0
- package/.hive/bootstrap-hive.js +118 -0
- package/.hive/deploy/api/Chart.yaml +6 -0
- package/.hive/deploy/api/staging.yaml +3 -0
- package/.hive/deploy/api/templates/deployment.yaml +44 -0
- package/.hive/deploy/api/templates/ingress.yaml +26 -0
- package/.hive/deploy/api/templates/service.yaml +14 -0
- package/.hive/deploy/script/Dockerfile +39 -0
- package/.hive/deploy/script/package-lock.json +1499 -0
- package/.hive/deploy/script/package.json +12 -0
- package/.hive/deploy/script/src/config.js +48 -0
- package/.hive/deploy/script/src/index.js +108 -0
- package/.hive/deploy/script/src/util.js +19 -0
- package/.hive/initial-data.json +176 -0
- package/.hive/package-lock.json +10242 -0
- package/.hive/package.json +98 -0
- package/.hive/ship_logo.png +0 -0
- package/.hive/src/app-config/app.js +3 -0
- package/.hive/src/app-config/assertEnv.js +15 -0
- package/.hive/src/app-config/index.js +62 -0
- package/.hive/src/app.js +69 -0
- package/.hive/src/assets/emails/components/header.mjml +13 -0
- package/.hive/src/assets/emails/dist/.gitkeep +0 -0
- package/.hive/src/assets/emails/signup-welcome.mjml +34 -0
- package/.hive/src/assets/emails/styles/index.mjml +77 -0
- package/.hive/src/autoMap/addHandlers.js +142 -0
- package/.hive/src/autoMap/getDependentFields.js +37 -0
- package/.hive/src/autoMap/mapSchema.js +99 -0
- package/.hive/src/autoMap/schemaMappings.js +13 -0
- package/.hive/src/autoMap/schemaMappings.json +3 -0
- package/.hive/src/bullMqBus.js +21 -0
- package/.hive/src/bullMqWrapper.js +23 -0
- package/.hive/src/db.js +52 -0
- package/.hive/src/emails/MyEmailComponent.jsx +14 -0
- package/.hive/src/emails/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/emails/compiled/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/helpers/db/ifUpdated.js +22 -0
- package/.hive/src/helpers/getMiddlewares.js +38 -0
- package/.hive/src/helpers/getResourceEndpoints.js +28 -0
- package/.hive/src/helpers/getResources.js +32 -0
- package/.hive/src/helpers/getSchemas.js +50 -0
- package/.hive/src/helpers/importHandlers.js +29 -0
- package/.hive/src/helpers/isZodArray.js +13 -0
- package/.hive/src/helpers/prettierFormat.js +8 -0
- package/.hive/src/helpers/schema/db.schema.js +9 -0
- package/.hive/src/helpers/schema/pagination.schema.js +14 -0
- package/.hive/src/ioEmitter.js +9 -0
- package/.hive/src/jsconfig.json +5 -0
- package/.hive/src/lib/node-mongo/.github/workflows/npm-publish.yml +32 -0
- package/.hive/src/lib/node-mongo/API.md +654 -0
- package/.hive/src/lib/node-mongo/CHANGELOG.md +98 -0
- package/.hive/src/lib/node-mongo/README.md +97 -0
- package/.hive/src/lib/node-mongo/package-lock.json +3682 -0
- package/.hive/src/lib/node-mongo/package.json +74 -0
- package/.hive/src/lib/node-mongo/src/index.js +64 -0
- package/.hive/src/lib/node-mongo/src/mongo-query-service.js +78 -0
- package/.hive/src/lib/node-mongo/src/mongo-service-error.js +15 -0
- package/.hive/src/lib/node-mongo/src/mongo-service.js +303 -0
- package/.hive/src/logger.js +43 -0
- package/.hive/src/middlewares/allowNoAuth.js +9 -0
- package/.hive/src/middlewares/attachUser.js +41 -0
- package/.hive/src/middlewares/global/extractUserTokens.js +15 -0
- package/.hive/src/middlewares/global/tryToAttachUser.js +33 -0
- package/.hive/src/middlewares/isAuthorized.js +18 -0
- package/.hive/src/middlewares/shouldExist.js +37 -0
- package/.hive/src/middlewares/shouldNotExist.js +19 -0
- package/.hive/src/middlewares/uploadFile.js +5 -0
- package/.hive/src/middlewares/validate.js +32 -0
- package/.hive/src/migrations/migration.js +8 -0
- package/.hive/src/migrations/migration.service.js +73 -0
- package/.hive/src/migrations/migrations/1.js +22 -0
- package/.hive/src/migrations/migrations-log/migration-log.schema.js +13 -0
- package/.hive/src/migrations/migrations-log/migration-log.service.js +50 -0
- package/.hive/src/migrations/migrations.schema.js +6 -0
- package/.hive/src/migrations/migrator.js +75 -0
- package/.hive/src/migrator.js +4 -0
- package/.hive/src/resources/_dev/endpoints/triggerSchedulerHandler.js +32 -0
- package/.hive/src/resources/health/endpoints/get.js +19 -0
- package/.hive/src/resources/schemaMappings/schemaMappings.schema.js +6 -0
- package/.hive/src/resources/tokens/methods/generateSecureToken.js +9 -0
- package/.hive/src/resources/tokens/methods/setToken.js +8 -0
- package/.hive/src/resources/tokens/methods/storeToken.js +35 -0
- package/.hive/src/resources/tokens/tokens.schema.js +11 -0
- package/.hive/src/resources/users/endpoints/getCurrentUser.js +14 -0
- package/.hive/src/resources/users/endpoints/getUserProfile.js +19 -0
- package/.hive/src/resources/users/handlers/test.js +1 -0
- package/.hive/src/resources/users/methods/ensureUserCreated.js +68 -0
- package/.hive/src/resources/users/users.schema.js +16 -0
- package/.hive/src/routes/index.js +172 -0
- package/.hive/src/routes/middlewares/attachCustomErrors.js +28 -0
- package/.hive/src/routes/middlewares/routeErrorHandler.js +27 -0
- package/.hive/src/scheduler/handlers/sendDailyReport.example.js +7 -0
- package/.hive/src/scheduler.js +32 -0
- package/.hive/src/security.util.js +38 -0
- package/.hive/src/services/emailService.js +15 -0
- package/.hive/src/services/globalTest.js +0 -0
- package/.hive/src/services/setCookie.js +21 -0
- package/.hive/src/socketIo.js +99 -0
- package/.hive/tsconfig.json +31 -0
- package/cli/helpers/docker.js +59 -0
- package/cli/helpers/envCheck.js +127 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +91 -17
- package/package.json +1 -1
- package/starter/loader.mjs +40 -0
- package/starter/package-lock.json +3512 -262
- package/starter/package.json +7 -2
- package/starter/register.mjs +6 -0
- package/starter/src/app-config/index.js +3 -0
- package/starter/src/app.js +10 -14
- package/starter/src/autoMap/addHandlers.js +3 -3
- package/starter/src/autoMap/getDependentFields.js +1 -1
- package/starter/src/autoMap/mapSchema.js +2 -2
- package/starter/src/bullMqBus.js +1 -1
- package/starter/src/bullMqWrapper.js +1 -1
- package/starter/src/db.js +12 -11
- package/starter/src/helpers/esm.js +56 -0
- package/starter/src/helpers/getMiddlewares.js +3 -0
- package/starter/src/helpers/getResourceEndpoints.js +3 -0
- package/starter/src/helpers/getResources.js +3 -0
- package/starter/src/helpers/getSchemas.js +3 -0
- package/starter/src/helpers/importHandlers.js +11 -20
- package/starter/src/ioEmitter.js +1 -1
- package/starter/src/logger.js +1 -1
- package/starter/src/middlewares/attachUser.js +2 -2
- package/starter/src/middlewares/global/tryToAttachUser.js +1 -1
- package/starter/src/middlewares/shouldExist.js +1 -1
- package/starter/src/middlewares/shouldNotExist.js +1 -1
- package/starter/src/migrations/migration.service.js +4 -1
- package/starter/src/migrations/migrations-log/migration-log.schema.js +1 -1
- package/starter/src/migrations/migrations-log/migration-log.service.js +1 -1
- package/starter/src/migrations/migrations.schema.js +1 -1
- package/starter/src/migrations/migrator.js +1 -1
- package/starter/src/migrator.js +2 -3
- package/starter/src/resources/schemaMappings/schemaMappings.schema.js +1 -1
- package/starter/src/resources/tokens/methods/setToken.js +1 -1
- package/starter/src/resources/tokens/methods/storeToken.js +2 -2
- package/starter/src/resources/tokens/tokens.schema.js +1 -1
- package/starter/src/resources/users/endpoints/getCurrentUser.js +1 -1
- package/starter/src/resources/users/endpoints/getUserProfile.js +1 -1
- package/starter/src/resources/users/methods/ensureUserCreated.js +1 -1
- package/starter/src/resources/users/users.schema.js +1 -1
- package/starter/src/routes/index.js +8 -8
- package/starter/src/routes/middlewares/routeErrorHandler.js +1 -1
- package/starter/src/scheduler.js +10 -7
- package/starter/src/services/emailService.js +1 -1
- package/starter/src/services/setCookie.js +2 -2
- package/starter/src/socketIo.js +3 -3
- package/test-app/.cursor/commands/add-endpoint.md +262 -0
- package/test-app/.cursor/commands/add-handler.md +137 -0
- package/test-app/.cursor/commands/add-middleware.md +95 -0
- package/test-app/.cursor/commands/add-resource.md +71 -0
- package/test-app/.cursor/commands/add-scheduler.md +138 -0
- package/test-app/.cursor/commands/add-service.md +188 -0
- package/test-app/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/test-app/.cursor/skills/hive-database/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/test-app/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/test-app/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/test-app/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/test-app/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/test-app/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/test-app/.cursor/skills/hive-service/SKILL.md +90 -0
- package/test-app/package-lock.json +462 -0
- package/test-app/package.json +21 -0
package/.hive/.babelrc
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Add Endpoint
|
|
2
|
+
|
|
3
|
+
Creates a new API endpoint with inferred types.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-endpoint {resource} [{name}] {method} [{url}] {fields}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-endpoint tasks post title, description, dueOn`
|
|
13
|
+
- `add-endpoint tasks get page, perPage, status`
|
|
14
|
+
- `add-endpoint tasks update /:id title, status`
|
|
15
|
+
- `add-endpoint companies addMember put /:companyId/members user: { _id }, role`
|
|
16
|
+
|
|
17
|
+
## Method Aliases
|
|
18
|
+
|
|
19
|
+
| Alias | HTTP Method | Default Name | Default URL |
|
|
20
|
+
|-------|-------------|--------------|-------------|
|
|
21
|
+
| `get` | GET | list | / |
|
|
22
|
+
| `post`, `create` | POST | create | / |
|
|
23
|
+
| `put`, `update` | PUT | update | /:id |
|
|
24
|
+
| `delete` | DELETE | delete | /:id |
|
|
25
|
+
|
|
26
|
+
## Type Inference Rules
|
|
27
|
+
|
|
28
|
+
Infer Zod types from field names:
|
|
29
|
+
|
|
30
|
+
| Pattern | Zod Type |
|
|
31
|
+
|---------|----------|
|
|
32
|
+
| `is*`, `has*`, `can*` (e.g. `isEmpty`, `hasAccess`) | `z.coerce.boolean().nullable().optional()` |
|
|
33
|
+
| `*On`, `*At` (e.g. `dueOn`, `createdAt`) | `z.coerce.date().nullable().optional()` |
|
|
34
|
+
| `page`, `perPage`, `limit`, `offset`, `count`, `*Count` | `z.coerce.number().default(...)` |
|
|
35
|
+
| `entity: { _id }`, `entity: { _id, name }` | `z.object({ ... }).nullable().optional()` |
|
|
36
|
+
| Everything else | `z.coerce.string().nullable().optional()` |
|
|
37
|
+
|
|
38
|
+
## Important Rules
|
|
39
|
+
|
|
40
|
+
1. **Services are always plural:** `db.services.companies`, `db.services.tasks`
|
|
41
|
+
2. **URL params from ctx.params:** `:id`, `:companyId` come from `ctx.params`, NOT in requestSchema
|
|
42
|
+
3. **Entity references as objects:** Use `user: { _id }` not `userId`. Full object with `_id` field required.
|
|
43
|
+
4. **Use shouldExist middleware:** To load entities by ID, use `shouldExist` middleware, not `findOne`
|
|
44
|
+
5. **Write fields explicitly:** Don't use spread `...updates`, list each field
|
|
45
|
+
|
|
46
|
+
## Location
|
|
47
|
+
|
|
48
|
+
`src/resources/{resource}/endpoints/{name}.js`
|
|
49
|
+
|
|
50
|
+
## Full Examples
|
|
51
|
+
|
|
52
|
+
### `add-endpoint tasks post title, description, project: { _id }, dueOn`
|
|
53
|
+
|
|
54
|
+
Creates `src/resources/tasks/endpoints/create.js`:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { z } from 'zod';
|
|
58
|
+
import db from 'db';
|
|
59
|
+
|
|
60
|
+
const tasksService = db.services.tasks;
|
|
61
|
+
|
|
62
|
+
export const middlewares = [];
|
|
63
|
+
|
|
64
|
+
export const requestSchema = z.object({
|
|
65
|
+
title: z.coerce.string().nullable().optional(),
|
|
66
|
+
description: z.coerce.string().nullable().optional(),
|
|
67
|
+
project: z.object({
|
|
68
|
+
_id: z.string(),
|
|
69
|
+
}).nullable().optional(),
|
|
70
|
+
dueOn: z.coerce.date().nullable().optional(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const handler = async (ctx) => {
|
|
74
|
+
const { title, description, project, dueOn } = ctx.validatedData;
|
|
75
|
+
|
|
76
|
+
return tasksService.create({
|
|
77
|
+
user: ctx.state.user,
|
|
78
|
+
title,
|
|
79
|
+
description,
|
|
80
|
+
project,
|
|
81
|
+
dueOn,
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const endpoint = {
|
|
86
|
+
url: '/',
|
|
87
|
+
method: 'post',
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `add-endpoint tasks get page, perPage, status, isCompleted`
|
|
92
|
+
|
|
93
|
+
Creates `src/resources/tasks/endpoints/list.js`:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import { z } from 'zod';
|
|
97
|
+
import db from 'db';
|
|
98
|
+
|
|
99
|
+
const tasksService = db.services.tasks;
|
|
100
|
+
|
|
101
|
+
export const middlewares = [];
|
|
102
|
+
|
|
103
|
+
export const requestSchema = z.object({
|
|
104
|
+
page: z.coerce.number().default(1),
|
|
105
|
+
perPage: z.coerce.number().default(20),
|
|
106
|
+
status: z.coerce.string().nullable().optional(),
|
|
107
|
+
isCompleted: z.coerce.boolean().nullable().optional(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const handler = async (ctx) => {
|
|
111
|
+
const { page, perPage, status, isCompleted } = ctx.validatedData;
|
|
112
|
+
|
|
113
|
+
return tasksService.find(
|
|
114
|
+
{ status, isCompleted },
|
|
115
|
+
{ page, perPage, sort: '-createdOn' }
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const endpoint = {
|
|
120
|
+
url: '/',
|
|
121
|
+
method: 'get',
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `add-endpoint tasks update /:id title, status, assignee: { _id, fullName }`
|
|
126
|
+
|
|
127
|
+
Creates `src/resources/tasks/endpoints/update.js`:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
import { z } from 'zod';
|
|
131
|
+
import db from 'db';
|
|
132
|
+
|
|
133
|
+
const tasksService = db.services.tasks;
|
|
134
|
+
|
|
135
|
+
export const middlewares = [];
|
|
136
|
+
|
|
137
|
+
export const requestSchema = z.object({
|
|
138
|
+
title: z.coerce.string().nullable().optional(),
|
|
139
|
+
status: z.coerce.string().nullable().optional(),
|
|
140
|
+
assignee: z.object({
|
|
141
|
+
_id: z.string(),
|
|
142
|
+
}).nullable().optional(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
export const handler = async (ctx) => {
|
|
146
|
+
const { title, status, assignee } = ctx.validatedData;
|
|
147
|
+
const { id } = ctx.params;
|
|
148
|
+
|
|
149
|
+
return tasksService.updateOne(
|
|
150
|
+
{ _id: id },
|
|
151
|
+
(doc) => ({
|
|
152
|
+
...doc,
|
|
153
|
+
title,
|
|
154
|
+
status,
|
|
155
|
+
assignee,
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const endpoint = {
|
|
161
|
+
url: '/:id',
|
|
162
|
+
method: 'put',
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `add-endpoint companies addMember put /:companyId/members user: { _id }, role`
|
|
167
|
+
|
|
168
|
+
Creates `src/resources/companies/endpoints/addMember.js`:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
import { z } from 'zod';
|
|
172
|
+
import db from 'db';
|
|
173
|
+
import shouldExist from 'middlewares/shouldExist';
|
|
174
|
+
|
|
175
|
+
const companiesService = db.services.companies;
|
|
176
|
+
|
|
177
|
+
export const middlewares = [
|
|
178
|
+
shouldExist('users', (ctx) => ctx.validatedData.user._id),
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
export const requestSchema = z.object({
|
|
182
|
+
user: z.object({
|
|
183
|
+
_id: z.string(),
|
|
184
|
+
}),
|
|
185
|
+
role: z.coerce.string().nullable().optional(),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export const handler = async (ctx) => {
|
|
189
|
+
const { user, role } = ctx.validatedData;
|
|
190
|
+
const { companyId } = ctx.params;
|
|
191
|
+
|
|
192
|
+
return companiesService.updateOne(
|
|
193
|
+
{ _id: companyId },
|
|
194
|
+
(doc) => ({
|
|
195
|
+
...doc,
|
|
196
|
+
members: [...(doc.members || []), { _id: user._id, role }],
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const endpoint = {
|
|
202
|
+
url: '/:companyId/members',
|
|
203
|
+
method: 'put',
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `add-endpoint auth login post email, password` (public)
|
|
208
|
+
|
|
209
|
+
Creates `src/resources/auth/endpoints/login.js`:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
import { z } from 'zod';
|
|
213
|
+
import db from 'db';
|
|
214
|
+
|
|
215
|
+
const usersService = db.services.users;
|
|
216
|
+
|
|
217
|
+
export const middlewares = ['allowNoAuth'];
|
|
218
|
+
|
|
219
|
+
export const requestSchema = z.object({
|
|
220
|
+
email: z.coerce.string(),
|
|
221
|
+
password: z.coerce.string(),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
export const handler = async (ctx) => {
|
|
225
|
+
const { email, password } = ctx.validatedData;
|
|
226
|
+
|
|
227
|
+
// Auth logic here
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const endpoint = {
|
|
231
|
+
url: '/login',
|
|
232
|
+
method: 'post',
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `add-endpoint tasks delete /:id`
|
|
237
|
+
|
|
238
|
+
Creates `src/resources/tasks/endpoints/delete.js`:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
import { z } from 'zod';
|
|
242
|
+
import db from 'db';
|
|
243
|
+
|
|
244
|
+
const tasksService = db.services.tasks;
|
|
245
|
+
|
|
246
|
+
export const middlewares = [];
|
|
247
|
+
|
|
248
|
+
export const requestSchema = z.object({});
|
|
249
|
+
|
|
250
|
+
export const handler = async (ctx) => {
|
|
251
|
+
const { id } = ctx.params;
|
|
252
|
+
|
|
253
|
+
await tasksService.remove({ _id: id });
|
|
254
|
+
|
|
255
|
+
return { success: true };
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const endpoint = {
|
|
259
|
+
url: '/:id',
|
|
260
|
+
method: 'delete',
|
|
261
|
+
};
|
|
262
|
+
```
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Add Handler
|
|
2
|
+
|
|
3
|
+
Creates a new event handler that reacts to database changes.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-handler {resource}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-handler tasks`
|
|
13
|
+
- `add-handler invoices`
|
|
14
|
+
- `add-handler messages`
|
|
15
|
+
|
|
16
|
+
## Location
|
|
17
|
+
|
|
18
|
+
`src/resources/{resource}/handlers/{handlerName}.js`
|
|
19
|
+
|
|
20
|
+
## Events
|
|
21
|
+
|
|
22
|
+
| Event | Payload | Trigger |
|
|
23
|
+
|-------|---------|---------|
|
|
24
|
+
| `created` | `{ doc }` | After `service.create()` |
|
|
25
|
+
| `updated` | `{ doc, prevDoc }` | After `service.updateOne/Many()` |
|
|
26
|
+
| `removed` | `{ doc }` | After `service.remove()` |
|
|
27
|
+
|
|
28
|
+
## Template
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
import db from 'db';
|
|
32
|
+
import ifUpdated from 'helpers/db/ifUpdated';
|
|
33
|
+
// import ioEmitter from 'ioEmitter';
|
|
34
|
+
|
|
35
|
+
const {resource}Service = db.services.{resource};
|
|
36
|
+
|
|
37
|
+
{resource}Service.on('created', async ({ doc }) => {
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
{resource}Service.on('updated', ifUpdated(['field'], async ({ doc, prevDoc }) => {
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
{resource}Service.on('removed', async ({ doc }) => {
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
{resource}Service.on('created', async ({ doc }) => {
|
|
48
|
+
ioEmitter.to(`room-${doc._id}`).emit('{resource}:created', { doc });
|
|
49
|
+
});
|
|
50
|
+
*/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### React to specific field changes
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
import db from 'db';
|
|
59
|
+
import ifUpdated from 'helpers/db/ifUpdated';
|
|
60
|
+
|
|
61
|
+
const tasksService = db.services.tasks;
|
|
62
|
+
|
|
63
|
+
tasksService.on('updated', ifUpdated(['status'], async ({ doc, prevDoc }) => {
|
|
64
|
+
// Only runs when status changed
|
|
65
|
+
}));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Emit socket event
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import db from 'db';
|
|
72
|
+
import ioEmitter from 'ioEmitter';
|
|
73
|
+
|
|
74
|
+
const messagesService = db.services.messages;
|
|
75
|
+
|
|
76
|
+
messagesService.on('created', async ({ doc: message }) => {
|
|
77
|
+
ioEmitter.to(`project-${message.project._id}`).emit('message:created', {
|
|
78
|
+
message,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Add related record
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import db from 'db';
|
|
87
|
+
|
|
88
|
+
const tasksService = db.services.tasks;
|
|
89
|
+
const notificationsService = db.services.notifications;
|
|
90
|
+
|
|
91
|
+
tasksService.on('created', async ({ doc }) => {
|
|
92
|
+
if (!doc.assignee?._id) return;
|
|
93
|
+
|
|
94
|
+
await notificationsService.create({
|
|
95
|
+
user: doc.assignee,
|
|
96
|
+
type: 'task.assigned',
|
|
97
|
+
data: { task: doc },
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Cleanup on delete
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import db from 'db';
|
|
106
|
+
|
|
107
|
+
const invoicesService = db.services.invoices;
|
|
108
|
+
const paymentsService = db.services.payments;
|
|
109
|
+
|
|
110
|
+
invoicesService.on('removed', async ({ doc }) => {
|
|
111
|
+
await paymentsService.remove({ 'invoice._id': doc._id });
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Bulk update (silent, no events)
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import db from 'db';
|
|
119
|
+
import ifUpdated from 'helpers/db/ifUpdated';
|
|
120
|
+
|
|
121
|
+
const usersService = db.services.users;
|
|
122
|
+
const tasksService = db.services.tasks;
|
|
123
|
+
|
|
124
|
+
usersService.on('updated', ifUpdated(['fullName', 'avatarUrl'], async ({ doc }) => {
|
|
125
|
+
await tasksService.atomic.update(
|
|
126
|
+
{ 'assignee._id': doc._id },
|
|
127
|
+
{ $set: { 'assignee.fullName': doc.fullName, 'assignee.avatarUrl': doc.avatarUrl } },
|
|
128
|
+
{ multi: true }
|
|
129
|
+
);
|
|
130
|
+
}));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Tips
|
|
134
|
+
|
|
135
|
+
- Use `ifUpdated` helper to react only when specific fields change
|
|
136
|
+
- Use `atomic.update` for bulk updates (no events triggered)
|
|
137
|
+
- Keep handlers fast — don't block the response
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Add Middleware
|
|
2
|
+
|
|
3
|
+
Creates a new middleware for endpoints.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-middleware {name}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-middleware isAdmin`
|
|
13
|
+
- `add-middleware canEditProject`
|
|
14
|
+
- `add-middleware rateLimit`
|
|
15
|
+
|
|
16
|
+
## Location
|
|
17
|
+
|
|
18
|
+
`src/middlewares/{name}.js`
|
|
19
|
+
|
|
20
|
+
## Template (simple)
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
export default async (ctx, next) => {
|
|
24
|
+
// Middleware logic here
|
|
25
|
+
|
|
26
|
+
return next();
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Template (with parameters)
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
export default (param) => async (ctx, next) => {
|
|
34
|
+
// Use param here
|
|
35
|
+
|
|
36
|
+
return next();
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### Access control
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
export default async (ctx, next) => {
|
|
46
|
+
if (!ctx.state.user?.isAdmin) {
|
|
47
|
+
ctx.throw(403, 'Admin access required');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return next();
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### With parameters
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import db from 'db';
|
|
58
|
+
|
|
59
|
+
export default (resource, getId = (ctx) => ctx.params.id) => async (ctx, next) => {
|
|
60
|
+
const id = getId(ctx);
|
|
61
|
+
const doc = await db.services[resource].findOne({ _id: id });
|
|
62
|
+
|
|
63
|
+
if (!doc) {
|
|
64
|
+
ctx.throw(404, `${resource} not found`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx.state[resource] = doc;
|
|
68
|
+
return next();
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### With runOrder (lower runs first)
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const middleware = async (ctx, next) => {
|
|
76
|
+
ctx.state.isSkipAuth = true;
|
|
77
|
+
return next();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
middleware.runOrder = -1;
|
|
81
|
+
|
|
82
|
+
export default middleware;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage in endpoints
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// Import and use directly
|
|
89
|
+
import isAdmin from 'middlewares/isAdmin';
|
|
90
|
+
export const middlewares = [isAdmin];
|
|
91
|
+
|
|
92
|
+
// With parameters
|
|
93
|
+
import canEdit from 'middlewares/canEdit';
|
|
94
|
+
export const middlewares = [canEdit('projects', (ctx) => ctx.params.projectId)];
|
|
95
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Add Resource
|
|
2
|
+
|
|
3
|
+
Creates a complete resource with schema and folder structure.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
1. Create folder: `src/resources/{name}/`
|
|
8
|
+
2. Create schema file: `src/resources/{name}/{name}.schema.js`
|
|
9
|
+
3. Create folders: `endpoints/`, `handlers/`, `methods/`
|
|
10
|
+
4. Create first endpoint: `src/resources/{name}/endpoints/list.js`
|
|
11
|
+
|
|
12
|
+
## Schema Template
|
|
13
|
+
|
|
14
|
+
**File:** `src/resources/{name}/{name}.schema.js`
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import dbSchema from 'helpers/schema/db.schema.js';
|
|
19
|
+
|
|
20
|
+
const schema = dbSchema.extend({
|
|
21
|
+
// Add fields here
|
|
22
|
+
title: z.coerce.string().nullable().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default schema;
|
|
26
|
+
|
|
27
|
+
export const secureFields = [];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## First Endpoint Template
|
|
31
|
+
|
|
32
|
+
**File:** `src/resources/{name}/endpoints/list.js`
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
import db from 'db';
|
|
37
|
+
|
|
38
|
+
const {name}Service = db.services.{name};
|
|
39
|
+
|
|
40
|
+
export const middlewares = [];
|
|
41
|
+
|
|
42
|
+
export const requestSchema = z.object({
|
|
43
|
+
page: z.coerce.number().default(1),
|
|
44
|
+
perPage: z.coerce.number().default(20),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const handler = async (ctx) => {
|
|
48
|
+
const { page, perPage } = ctx.validatedData;
|
|
49
|
+
|
|
50
|
+
return {name}Service.find(
|
|
51
|
+
{},
|
|
52
|
+
{ page, perPage, sort: '-createdOn' }
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const endpoint = {
|
|
57
|
+
url: '/',
|
|
58
|
+
method: 'get',
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Result
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/resources/{name}/
|
|
66
|
+
├── {name}.schema.js
|
|
67
|
+
├── endpoints/
|
|
68
|
+
│ └── list.js
|
|
69
|
+
├── handlers/
|
|
70
|
+
└── methods/
|
|
71
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Add Scheduler
|
|
2
|
+
|
|
3
|
+
Creates a new scheduled background job.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-scheduler {name} [{schedule}]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-scheduler syncExternalData every 5 minutes`
|
|
13
|
+
- `add-scheduler sendDailyReport daily at 9am`
|
|
14
|
+
- `add-scheduler markOverdueInvoices every day at midnight`
|
|
15
|
+
- `add-scheduler weeklyCleanup every monday at 3am`
|
|
16
|
+
|
|
17
|
+
## Schedule to Cron
|
|
18
|
+
|
|
19
|
+
Translate natural language to cron:
|
|
20
|
+
|
|
21
|
+
| User says | Cron |
|
|
22
|
+
|-----------|------|
|
|
23
|
+
| every minute | `* * * * *` |
|
|
24
|
+
| every 5 minutes | `*/5 * * * *` |
|
|
25
|
+
| every hour | `0 * * * *` |
|
|
26
|
+
| every 6 hours | `0 */6 * * *` |
|
|
27
|
+
| daily at midnight | `0 0 * * *` |
|
|
28
|
+
| daily at 9am | `0 9 * * *` |
|
|
29
|
+
| every monday at 9am | `0 9 * * 1` |
|
|
30
|
+
| first of month | `0 0 1 * *` |
|
|
31
|
+
|
|
32
|
+
## Location
|
|
33
|
+
|
|
34
|
+
`src/scheduler/handlers/{name}.js`
|
|
35
|
+
|
|
36
|
+
## Template
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import db from 'db';
|
|
40
|
+
|
|
41
|
+
export const handler = async () => {
|
|
42
|
+
// Job logic here
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const cron = '0 * * * *'; // Every hour
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
### Sync external data
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import db from 'db';
|
|
54
|
+
import moment from 'moment';
|
|
55
|
+
import externalApi from 'services/externalApi';
|
|
56
|
+
|
|
57
|
+
const itemsService = db.services.items;
|
|
58
|
+
|
|
59
|
+
export const handler = async () => {
|
|
60
|
+
const items = await externalApi.list({
|
|
61
|
+
updatedSince: moment().subtract(5, 'minutes').toDate(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
for (const item of items) {
|
|
65
|
+
await itemsService.updateOne(
|
|
66
|
+
{ externalId: item.id },
|
|
67
|
+
(doc) => ({ ...doc, ...item })
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const cron = '*/5 * * * *';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Send daily report
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
import db from 'db';
|
|
79
|
+
import slack from 'services/slack';
|
|
80
|
+
|
|
81
|
+
const tasksService = db.services.tasks;
|
|
82
|
+
|
|
83
|
+
export const handler = async () => {
|
|
84
|
+
const { count: completed } = await tasksService.find({ status: 'completed' });
|
|
85
|
+
const { count: pending } = await tasksService.find({ status: 'pending' });
|
|
86
|
+
|
|
87
|
+
await slack.sendChannelMessage({
|
|
88
|
+
channel: '#reports',
|
|
89
|
+
text: `Daily report: ${completed} completed, ${pending} pending`,
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const cron = '0 9 * * *';
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Mark overdue invoices
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
import db from 'db';
|
|
100
|
+
|
|
101
|
+
const invoicesService = db.services.invoices;
|
|
102
|
+
|
|
103
|
+
export const handler = async () => {
|
|
104
|
+
await invoicesService.updateMany(
|
|
105
|
+
{
|
|
106
|
+
isPaid: { $ne: true },
|
|
107
|
+
isOverdue: { $ne: true },
|
|
108
|
+
dueOn: { $lt: new Date() },
|
|
109
|
+
},
|
|
110
|
+
(doc) => ({ ...doc, isOverdue: true })
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const cron = '0 0 * * *';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Cleanup old records
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import db from 'db';
|
|
121
|
+
import moment from 'moment';
|
|
122
|
+
|
|
123
|
+
const logsService = db.services.logs;
|
|
124
|
+
|
|
125
|
+
export const handler = async () => {
|
|
126
|
+
await logsService.remove({
|
|
127
|
+
createdOn: { $lt: moment().subtract(30, 'days').toDate() },
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const cron = '0 3 * * *';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Tips
|
|
135
|
+
|
|
136
|
+
- Keep jobs idempotent (safe to re-run)
|
|
137
|
+
- Log progress for debugging
|
|
138
|
+
- Use `atomic.update` for bulk operations
|