@paralect/hive 0.1.47 → 0.1.49

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.
@@ -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