@paralect/hive 0.1.49 → 0.1.50-alpha.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/.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 +123 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +84 -12
- package/package.json +1 -1
- 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 +8684 -0
- package/test-app/package.json +21 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Add Service
|
|
2
|
+
|
|
3
|
+
Creates a new service for external APIs or utilities.
|
|
4
|
+
|
|
5
|
+
## Command Format
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
add-service {name} [{methods}]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
- `add-service slack sendChannelMessage, replyToThread`
|
|
13
|
+
- `add-service stripe createCustomer, createPaymentIntent, listInvoices`
|
|
14
|
+
- `add-service openai chat, generateImage`
|
|
15
|
+
- `add-service sendgrid sendEmail, sendTemplateEmail`
|
|
16
|
+
|
|
17
|
+
## Location
|
|
18
|
+
|
|
19
|
+
`src/services/{name}.js`
|
|
20
|
+
|
|
21
|
+
## Known Services & Libraries
|
|
22
|
+
|
|
23
|
+
When creating a service, use these go-to libraries:
|
|
24
|
+
|
|
25
|
+
| Service | Library | Install |
|
|
26
|
+
|---------|---------|---------|
|
|
27
|
+
| Slack | `@slack/web-api` | `npm i @slack/web-api` |
|
|
28
|
+
| Stripe | `stripe` | `npm i stripe` |
|
|
29
|
+
| OpenAI | `openai` | `npm i openai` |
|
|
30
|
+
| SendGrid | `@sendgrid/mail` | `npm i @sendgrid/mail` |
|
|
31
|
+
| Twilio | `twilio` | `npm i twilio` |
|
|
32
|
+
| AWS S3 | `@aws-sdk/client-s3` | `npm i @aws-sdk/client-s3` |
|
|
33
|
+
| Resend | `resend` | `npm i resend` |
|
|
34
|
+
| Postmark | `postmark` | `npm i postmark` |
|
|
35
|
+
|
|
36
|
+
## Template
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import config from 'app-config';
|
|
40
|
+
|
|
41
|
+
// Initialize client here
|
|
42
|
+
|
|
43
|
+
export default {
|
|
44
|
+
client,
|
|
45
|
+
|
|
46
|
+
// Methods here
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Full Examples
|
|
51
|
+
|
|
52
|
+
### `add-service slack sendChannelMessage, replyToThread`
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import config from 'app-config';
|
|
56
|
+
import { WebClient } from '@slack/web-api';
|
|
57
|
+
|
|
58
|
+
const client = new WebClient(config.slack.botToken);
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
client,
|
|
62
|
+
|
|
63
|
+
sendChannelMessage: async ({ channel, text, blocks }) => {
|
|
64
|
+
return client.chat.postMessage({ channel, text, blocks });
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
replyToThread: async ({ channel, threadTs, text }) => {
|
|
68
|
+
return client.chat.postMessage({ channel, text, thread_ts: threadTs });
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `add-service stripe createCustomer, createPaymentIntent, listInvoices`
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import config from 'app-config';
|
|
77
|
+
import Stripe from 'stripe';
|
|
78
|
+
|
|
79
|
+
const client = new Stripe(config.stripe.secretKey);
|
|
80
|
+
|
|
81
|
+
export default {
|
|
82
|
+
client,
|
|
83
|
+
|
|
84
|
+
createCustomer: async ({ email, name, metadata }) => {
|
|
85
|
+
return client.customers.create({ email, name, metadata });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
createPaymentIntent: async ({ amount, currency, customerId }) => {
|
|
89
|
+
return client.paymentIntents.create({
|
|
90
|
+
amount,
|
|
91
|
+
currency,
|
|
92
|
+
customer: customerId,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
listInvoices: async ({ customerId, limit = 10 }) => {
|
|
97
|
+
return client.invoices.list({ customer: customerId, limit });
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `add-service openai chat, generateImage`
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import config from 'app-config';
|
|
106
|
+
import OpenAI from 'openai';
|
|
107
|
+
|
|
108
|
+
const client = new OpenAI({ apiKey: config.openai.apiKey });
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
client,
|
|
112
|
+
|
|
113
|
+
chat: async ({ messages, model = 'gpt-4' }) => {
|
|
114
|
+
const response = await client.chat.completions.create({ model, messages });
|
|
115
|
+
return response.choices[0].message;
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
generateImage: async ({ prompt, size = '1024x1024' }) => {
|
|
119
|
+
const response = await client.images.generate({ prompt, size, n: 1 });
|
|
120
|
+
return response.data[0].url;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `add-service sendgrid sendEmail, sendTemplateEmail`
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import config from 'app-config';
|
|
129
|
+
import sgMail from '@sendgrid/mail';
|
|
130
|
+
|
|
131
|
+
sgMail.setApiKey(config.sendgrid.apiKey);
|
|
132
|
+
|
|
133
|
+
export default {
|
|
134
|
+
sendEmail: async ({ to, from, subject, html }) => {
|
|
135
|
+
return sgMail.send({ to, from, subject, html });
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
sendTemplateEmail: async ({ to, from, templateId, dynamicTemplateData }) => {
|
|
139
|
+
return sgMail.send({ to, from, templateId, dynamicTemplateData });
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `add-service s3 upload, getSignedUrl, delete`
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
import config from 'app-config';
|
|
148
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
149
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
150
|
+
|
|
151
|
+
const client = new S3Client({
|
|
152
|
+
region: config.aws.region,
|
|
153
|
+
credentials: {
|
|
154
|
+
accessKeyId: config.aws.accessKeyId,
|
|
155
|
+
secretAccessKey: config.aws.secretAccessKey,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export default {
|
|
160
|
+
client,
|
|
161
|
+
|
|
162
|
+
upload: async ({ bucket, key, body, contentType }) => {
|
|
163
|
+
const command = new PutObjectCommand({ Bucket: bucket, Key: key, Body: body, ContentType: contentType });
|
|
164
|
+
return client.send(command);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
getSignedUrl: async ({ bucket, key, expiresIn = 3600 }) => {
|
|
168
|
+
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
|
|
169
|
+
return getSignedUrl(client, command, { expiresIn });
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
delete: async ({ bucket, key }) => {
|
|
173
|
+
const command = new DeleteObjectCommand({ Bucket: bucket, Key: key });
|
|
174
|
+
return client.send(command);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Usage
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
import slack from 'services/slack';
|
|
183
|
+
import stripe from 'services/stripe';
|
|
184
|
+
|
|
185
|
+
await slack.sendChannelMessage({ channel: '#general', text: 'Hello' });
|
|
186
|
+
|
|
187
|
+
const customer = await stripe.createCustomer({ email: 'user@example.com' });
|
|
188
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-auth
|
|
3
|
+
description: How authentication works in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/resources/auth/**/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Authentication
|
|
10
|
+
|
|
11
|
+
Hive provides auth infrastructure but **no built-in auth endpoints**. You implement login/signup yourself.
|
|
12
|
+
|
|
13
|
+
## Built-in (in .hive/)
|
|
14
|
+
|
|
15
|
+
- `tokens` collection - stores access tokens
|
|
16
|
+
- `attachUser` middleware - loads user from token
|
|
17
|
+
- `isAuthorized` middleware - requires auth
|
|
18
|
+
- `allowNoAuth` middleware - skips auth
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
1. Token extracted from cookie `access_token` or `Authorization: Bearer` header
|
|
23
|
+
2. Token looked up in `tokens` collection
|
|
24
|
+
3. User loaded from `users` collection
|
|
25
|
+
4. User attached to `ctx.state.user`
|
|
26
|
+
|
|
27
|
+
## Token Schema (built-in)
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
{
|
|
31
|
+
_id: string,
|
|
32
|
+
user: { _id: string },
|
|
33
|
+
token: string,
|
|
34
|
+
metadata: object (optional),
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Implement Auth Endpoints
|
|
39
|
+
|
|
40
|
+
**1. Create token helper:**
|
|
41
|
+
```javascript
|
|
42
|
+
// src/resources/auth/methods/createToken.js
|
|
43
|
+
import db from 'db';
|
|
44
|
+
import crypto from 'crypto';
|
|
45
|
+
|
|
46
|
+
const tokenService = db.services.tokens;
|
|
47
|
+
|
|
48
|
+
export default async (ctx, { userId, metadata }) => {
|
|
49
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
50
|
+
|
|
51
|
+
await tokenService.create({
|
|
52
|
+
token,
|
|
53
|
+
user: { _id: userId },
|
|
54
|
+
...(metadata && { metadata }),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
ctx.cookies.set('access_token', token, {
|
|
58
|
+
httpOnly: false,
|
|
59
|
+
expires: new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { token };
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**2. Login endpoint:**
|
|
67
|
+
```javascript
|
|
68
|
+
// src/resources/auth/endpoints/login.js
|
|
69
|
+
import { z } from 'zod';
|
|
70
|
+
import db from 'db';
|
|
71
|
+
import bcrypt from 'bcrypt';
|
|
72
|
+
import createToken from '../methods/createToken';
|
|
73
|
+
|
|
74
|
+
export const handler = async (ctx) => {
|
|
75
|
+
const { email, password } = ctx.validatedData;
|
|
76
|
+
|
|
77
|
+
const user = await db.services.users.findOne(
|
|
78
|
+
{ email },
|
|
79
|
+
{ isIncludeSecureFields: true }
|
|
80
|
+
);
|
|
81
|
+
ctx.assert(user, 401, 'Invalid credentials');
|
|
82
|
+
|
|
83
|
+
const valid = await bcrypt.compare(password, user.password);
|
|
84
|
+
ctx.assert(valid, 401, 'Invalid credentials');
|
|
85
|
+
|
|
86
|
+
const { token } = await createToken(ctx, { userId: user._id });
|
|
87
|
+
return { user, token };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const middlewares = ['allowNoAuth'];
|
|
91
|
+
export const endpoint = { url: '/login', method: 'post' };
|
|
92
|
+
export const requestSchema = z.object({
|
|
93
|
+
email: z.string().email(),
|
|
94
|
+
password: z.string(),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**3. Logout endpoint:**
|
|
99
|
+
```javascript
|
|
100
|
+
// src/resources/auth/endpoints/logout.js
|
|
101
|
+
import { z } from 'zod';
|
|
102
|
+
import db from 'db';
|
|
103
|
+
|
|
104
|
+
export const handler = async (ctx) => {
|
|
105
|
+
await db.services.tokens.remove({ token: ctx.state.accessToken });
|
|
106
|
+
ctx.cookies.set('access_token', null);
|
|
107
|
+
return { success: true };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const endpoint = { url: '/logout', method: 'post' };
|
|
111
|
+
export const requestSchema = z.object({});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Users Schema (add password)
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// src/resources/users/users.schema.js
|
|
118
|
+
password: z.coerce.string().nullable().optional(),
|
|
119
|
+
|
|
120
|
+
// Hide from responses
|
|
121
|
+
export const secureFields = ['password'];
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Client Usage
|
|
125
|
+
|
|
126
|
+
**Browser (cookies):**
|
|
127
|
+
```javascript
|
|
128
|
+
await fetch('/auth/login', { method: 'POST', credentials: 'include', body });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**API (header):**
|
|
132
|
+
```javascript
|
|
133
|
+
await fetch('/tasks', { headers: { Authorization: `Bearer ${token}` } });
|
|
134
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-database
|
|
3
|
+
description: Database operations in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/**/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Database Operations
|
|
10
|
+
|
|
11
|
+
## Access Service
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import db from 'db';
|
|
15
|
+
const taskService = db.services.tasks; // From schema name
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Find Operations
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// Find many
|
|
22
|
+
const { results, pagesCount, count } = await service.find(
|
|
23
|
+
{ status: 'active' },
|
|
24
|
+
{ page: 1, perPage: 20, sort: '-createdOn', fields: ['_id', 'title'] }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Find one
|
|
28
|
+
const doc = await service.findOne({ _id: id });
|
|
29
|
+
|
|
30
|
+
// Check exists
|
|
31
|
+
const exists = await service.exists({ _id: id });
|
|
32
|
+
|
|
33
|
+
// Count
|
|
34
|
+
const total = await service.count({ status: 'active' });
|
|
35
|
+
|
|
36
|
+
// Distinct values
|
|
37
|
+
const statuses = await service.distinct('status');
|
|
38
|
+
|
|
39
|
+
// Aggregate
|
|
40
|
+
const { results } = await service.aggregate([
|
|
41
|
+
{ $match: { status: 'done' } },
|
|
42
|
+
{ $group: { _id: '$project._id', count: { $sum: 1 } } },
|
|
43
|
+
]);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Write Operations
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// Create
|
|
50
|
+
const doc = await service.create({ title: 'New', user: ctx.state.user });
|
|
51
|
+
|
|
52
|
+
// Update one (must use function)
|
|
53
|
+
const updated = await service.updateOne(
|
|
54
|
+
{ _id: id },
|
|
55
|
+
(doc) => ({ ...doc, title: 'Updated' })
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Update many
|
|
59
|
+
await service.updateMany(
|
|
60
|
+
{ status: 'pending' },
|
|
61
|
+
(doc) => ({ ...doc, status: 'active' })
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Remove
|
|
65
|
+
await service.remove({ _id: id });
|
|
66
|
+
|
|
67
|
+
// Generate ID
|
|
68
|
+
const newId = service.generateId();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Atomic Operations (No Events)
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Silent bulk update
|
|
75
|
+
await service.atomic.update(
|
|
76
|
+
{ 'project._id': projectId },
|
|
77
|
+
{ $set: { 'project.name': newName } },
|
|
78
|
+
{ multi: true }
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Query Patterns
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { when } from 'services/utils';
|
|
86
|
+
|
|
87
|
+
const { results } = await service.find({
|
|
88
|
+
// Conditional fields
|
|
89
|
+
...when(status, { status }),
|
|
90
|
+
...when(userId, { 'user._id': userId }),
|
|
91
|
+
|
|
92
|
+
// Complex conditions
|
|
93
|
+
status: { $in: ['active', 'pending'] },
|
|
94
|
+
createdOn: { $gte: startDate, $lt: endDate },
|
|
95
|
+
$or: [{ 'user._id': id }, { isPublic: true }],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Rules
|
|
100
|
+
|
|
101
|
+
- `updateOne` requires a function, not an object
|
|
102
|
+
- Use `atomic.update` for bulk/silent updates
|
|
103
|
+
- Use `when()` helper for conditional query building
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-endpoint
|
|
3
|
+
description: How to create API endpoints in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/resources/**/endpoints/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Creating Endpoints
|
|
10
|
+
|
|
11
|
+
Location: `/src/resources/{name}/endpoints/{action}.js`
|
|
12
|
+
|
|
13
|
+
## Template (4 Required Exports)
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import db from 'db';
|
|
18
|
+
|
|
19
|
+
const myService = db.services.myResource;
|
|
20
|
+
|
|
21
|
+
export const handler = async (ctx) => {
|
|
22
|
+
const { param } = ctx.validatedData;
|
|
23
|
+
// Logic here
|
|
24
|
+
return { result: 'data' };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const middlewares = [];
|
|
28
|
+
|
|
29
|
+
export const endpoint = {
|
|
30
|
+
url: '/',
|
|
31
|
+
method: 'get',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const requestSchema = z.object({
|
|
35
|
+
param: z.coerce.string().nullable().optional(),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Handler Context (ctx)
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
ctx.validatedData // Validated input (body + query + params)
|
|
43
|
+
ctx.state.user // Authenticated user
|
|
44
|
+
ctx.params // URL params
|
|
45
|
+
ctx.throw(404, 'msg') // Throw error
|
|
46
|
+
ctx.assert(cond, 400) // Assert or throw
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Common Patterns
|
|
50
|
+
|
|
51
|
+
**List:**
|
|
52
|
+
```javascript
|
|
53
|
+
export const handler = async (ctx) => {
|
|
54
|
+
const { page, perPage } = ctx.validatedData;
|
|
55
|
+
const { results, count } = await service.find(
|
|
56
|
+
{ 'user._id': ctx.state.user._id },
|
|
57
|
+
{ page, perPage, sort: '-createdOn' }
|
|
58
|
+
);
|
|
59
|
+
return { results, count };
|
|
60
|
+
};
|
|
61
|
+
export const endpoint = { url: '/', method: 'get' };
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Create:**
|
|
65
|
+
```javascript
|
|
66
|
+
export const handler = async (ctx) => {
|
|
67
|
+
const doc = await service.create({
|
|
68
|
+
...ctx.validatedData,
|
|
69
|
+
user: ctx.state.user,
|
|
70
|
+
});
|
|
71
|
+
return doc;
|
|
72
|
+
};
|
|
73
|
+
export const endpoint = { url: '/', method: 'post' };
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Update:**
|
|
77
|
+
```javascript
|
|
78
|
+
export const handler = async (ctx) => {
|
|
79
|
+
const { id, title } = ctx.validatedData;
|
|
80
|
+
return await service.updateOne({ _id: id }, (doc) => ({ ...doc, title }));
|
|
81
|
+
};
|
|
82
|
+
export const endpoint = { url: '/:id', method: 'put' };
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Middlewares
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// By name
|
|
89
|
+
export const middlewares = ['allowNoAuth'];
|
|
90
|
+
|
|
91
|
+
// With args
|
|
92
|
+
export const middlewares = [{ name: 'shouldExist', args: ['projects'] }];
|
|
93
|
+
|
|
94
|
+
// Direct function
|
|
95
|
+
import canEditProject from 'middlewares/canEditProject';
|
|
96
|
+
export const middlewares = [canEditProject((ctx) => ctx.params.projectId)];
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Public Endpoint (No Auth)
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
export const middlewares = ['allowNoAuth'];
|
|
103
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-handler
|
|
3
|
+
description: How to create event handlers in Hive framework
|
|
4
|
+
globs:
|
|
5
|
+
- src/resources/**/handlers/*.js
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Creating Event Handlers
|
|
10
|
+
|
|
11
|
+
Handlers react to database events automatically.
|
|
12
|
+
|
|
13
|
+
Location: `/src/resources/{name}/handlers/{handlerName}.js`
|
|
14
|
+
|
|
15
|
+
## Template
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import db from 'db';
|
|
19
|
+
|
|
20
|
+
const myService = db.services.myResource;
|
|
21
|
+
|
|
22
|
+
myService.on('created', async ({ doc }) => {
|
|
23
|
+
// Handle creation
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
myService.on('updated', async ({ doc, prevDoc }) => {
|
|
27
|
+
// Handle update
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
myService.on('removed', async ({ doc }) => {
|
|
31
|
+
// Handle removal
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Events
|
|
36
|
+
|
|
37
|
+
| Event | Payload | Trigger |
|
|
38
|
+
|-------|---------|---------|
|
|
39
|
+
| `created` | `{ doc }` | After `service.create()` |
|
|
40
|
+
| `updated` | `{ doc, prevDoc }` | After `service.updateOne/Many()` |
|
|
41
|
+
| `removed` | `{ doc }` | After `service.remove()` |
|
|
42
|
+
|
|
43
|
+
## Common Patterns
|
|
44
|
+
|
|
45
|
+
**Create related record:**
|
|
46
|
+
```javascript
|
|
47
|
+
taskService.on('created', async ({ doc }) => {
|
|
48
|
+
await eventService.create({
|
|
49
|
+
type: 'task.created',
|
|
50
|
+
data: { task: doc },
|
|
51
|
+
project: doc.project,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Update parent on child change:**
|
|
57
|
+
```javascript
|
|
58
|
+
docService.on('created', async ({ doc }) => {
|
|
59
|
+
if (doc.role === 'case-study') {
|
|
60
|
+
await projectService.updateOne(
|
|
61
|
+
{ _id: doc.project._id },
|
|
62
|
+
(p) => ({ ...p, caseStudy: doc })
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**React to specific field change:**
|
|
69
|
+
```javascript
|
|
70
|
+
import ifUpdated from 'helpers/db/ifUpdated';
|
|
71
|
+
|
|
72
|
+
taskService.on('updated', ifUpdated(['status'], async ({ doc, prevDoc }) => {
|
|
73
|
+
// Only runs when status changed
|
|
74
|
+
}));
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Cleanup on delete:**
|
|
78
|
+
```javascript
|
|
79
|
+
docService.on('removed', async ({ doc }) => {
|
|
80
|
+
await eventService.remove({ 'data.doc._id': doc._id });
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Rules
|
|
85
|
+
|
|
86
|
+
- Keep handlers fast (don't block response)
|
|
87
|
+
- Use `atomic.update` for silent bulk updates (no events)
|
|
88
|
+
- Use `ifUpdated` helper to filter field changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hive-mapping
|
|
3
|
+
description: Schema mappings for auto-syncing embedded documents
|
|
4
|
+
globs:
|
|
5
|
+
- src/autoMap/schemaMappings.json
|
|
6
|
+
alwaysApply: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Schema Mappings
|
|
10
|
+
|
|
11
|
+
Auto-sync embedded references when source documents change.
|
|
12
|
+
|
|
13
|
+
Location: `/src/autoMap/schemaMappings.json`
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
1. You embed document references in schemas (e.g., `user: { _id, fullName }`)
|
|
18
|
+
2. You register the mapping in `schemaMappings.json`
|
|
19
|
+
3. When source document updates, all referencing documents sync automatically
|
|
20
|
+
|
|
21
|
+
## Config Format
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"tasks": {
|
|
26
|
+
"user": {
|
|
27
|
+
"schema": "users"
|
|
28
|
+
},
|
|
29
|
+
"project": {
|
|
30
|
+
"schema": "projects"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Means: In `tasks` collection, fields `user` and `project` reference `users` and `projects` collections.
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
**Schema with embedded reference:**
|
|
41
|
+
```javascript
|
|
42
|
+
// tasks.schema.js
|
|
43
|
+
project: z
|
|
44
|
+
.object({
|
|
45
|
+
_id: z.string(),
|
|
46
|
+
name: z.coerce.string().nullable().optional(),
|
|
47
|
+
logoUrl: z.coerce.string().nullable().optional(),
|
|
48
|
+
})
|
|
49
|
+
.nullable()
|
|
50
|
+
.optional(),
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Mapping config:**
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"tasks": {
|
|
57
|
+
"project": {
|
|
58
|
+
"schema": "projects"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Result:** When project's `name` or `logoUrl` changes, all tasks with that project update automatically.
|
|
65
|
+
|
|
66
|
+
## Adding New Mapping
|
|
67
|
+
|
|
68
|
+
1. Define embedded reference in schema (include fields to sync)
|
|
69
|
+
2. Add entry to `schemaMappings.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"yourResource": {
|
|
74
|
+
"fieldName": {
|
|
75
|
+
"schema": "sourceSchema"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
|
|
83
|
+
- Only fields defined in the embedded object shape are synced
|
|
84
|
+
- Works for both single references and arrays
|
|
85
|
+
- Changes sync on source document `updated` event
|