@paralect/hive 0.1.48 → 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.
Files changed (165) hide show
  1. package/.cursor/commands/deslop.md +12 -0
  2. package/.hive/.babelrc +3 -0
  3. package/.hive/.cursor/commands/add-endpoint.md +262 -0
  4. package/.hive/.cursor/commands/add-handler.md +137 -0
  5. package/.hive/.cursor/commands/add-middleware.md +95 -0
  6. package/.hive/.cursor/commands/add-resource.md +71 -0
  7. package/.hive/.cursor/commands/add-scheduler.md +138 -0
  8. package/.hive/.cursor/commands/add-service.md +188 -0
  9. package/.hive/.cursor/skills/hive-auth/SKILL.md +134 -0
  10. package/.hive/.cursor/skills/hive-database/SKILL.md +103 -0
  11. package/.hive/.cursor/skills/hive-endpoint/SKILL.md +103 -0
  12. package/.hive/.cursor/skills/hive-handler/SKILL.md +88 -0
  13. package/.hive/.cursor/skills/hive-mapping/SKILL.md +85 -0
  14. package/.hive/.cursor/skills/hive-middleware/SKILL.md +104 -0
  15. package/.hive/.cursor/skills/hive-overview/SKILL.md +50 -0
  16. package/.hive/.cursor/skills/hive-scheduler/SKILL.md +94 -0
  17. package/.hive/.cursor/skills/hive-schema/SKILL.md +73 -0
  18. package/.hive/.cursor/skills/hive-service/SKILL.md +90 -0
  19. package/.hive/.dockerignore +1 -0
  20. package/.hive/Dockerfile +22 -0
  21. package/.hive/Dockerfile.dev +33 -0
  22. package/.hive/Dockerfile.prod +29 -0
  23. package/.hive/README.md +11 -0
  24. package/.hive/bin/deploy.sh +5 -0
  25. package/.hive/bin/start.sh +2 -0
  26. package/.hive/bootstrap-hive.js +118 -0
  27. package/.hive/deploy/api/Chart.yaml +6 -0
  28. package/.hive/deploy/api/staging.yaml +3 -0
  29. package/.hive/deploy/api/templates/deployment.yaml +44 -0
  30. package/.hive/deploy/api/templates/ingress.yaml +26 -0
  31. package/.hive/deploy/api/templates/service.yaml +14 -0
  32. package/.hive/deploy/script/Dockerfile +39 -0
  33. package/.hive/deploy/script/package-lock.json +1499 -0
  34. package/.hive/deploy/script/package.json +12 -0
  35. package/.hive/deploy/script/src/config.js +48 -0
  36. package/.hive/deploy/script/src/index.js +108 -0
  37. package/.hive/deploy/script/src/util.js +19 -0
  38. package/.hive/initial-data.json +176 -0
  39. package/.hive/package-lock.json +10242 -0
  40. package/.hive/package.json +98 -0
  41. package/.hive/ship_logo.png +0 -0
  42. package/.hive/src/app-config/app.js +3 -0
  43. package/.hive/src/app-config/assertEnv.js +15 -0
  44. package/.hive/src/app-config/index.js +62 -0
  45. package/.hive/src/app.js +69 -0
  46. package/.hive/src/assets/emails/components/header.mjml +13 -0
  47. package/.hive/src/assets/emails/dist/.gitkeep +0 -0
  48. package/.hive/src/assets/emails/signup-welcome.mjml +34 -0
  49. package/.hive/src/assets/emails/styles/index.mjml +77 -0
  50. package/.hive/src/autoMap/addHandlers.js +142 -0
  51. package/.hive/src/autoMap/getDependentFields.js +37 -0
  52. package/.hive/src/autoMap/mapSchema.js +99 -0
  53. package/.hive/src/autoMap/schemaMappings.js +13 -0
  54. package/.hive/src/autoMap/schemaMappings.json +3 -0
  55. package/.hive/src/bullMqBus.js +21 -0
  56. package/.hive/src/bullMqWrapper.js +23 -0
  57. package/.hive/src/db.js +52 -0
  58. package/.hive/src/emails/MyEmailComponent.jsx +14 -0
  59. package/.hive/src/emails/compiled/MyEmailComponent.js +18 -0
  60. package/.hive/src/emails/compiled/compiled/MyEmailComponent.js +18 -0
  61. package/.hive/src/helpers/db/ifUpdated.js +22 -0
  62. package/.hive/src/helpers/getMiddlewares.js +38 -0
  63. package/.hive/src/helpers/getResourceEndpoints.js +28 -0
  64. package/.hive/src/helpers/getResources.js +32 -0
  65. package/.hive/src/helpers/getSchemas.js +50 -0
  66. package/.hive/src/helpers/importHandlers.js +29 -0
  67. package/.hive/src/helpers/isZodArray.js +13 -0
  68. package/.hive/src/helpers/prettierFormat.js +8 -0
  69. package/.hive/src/helpers/schema/db.schema.js +9 -0
  70. package/.hive/src/helpers/schema/pagination.schema.js +14 -0
  71. package/.hive/src/ioEmitter.js +9 -0
  72. package/.hive/src/jsconfig.json +5 -0
  73. package/.hive/src/lib/node-mongo/.github/workflows/npm-publish.yml +32 -0
  74. package/.hive/src/lib/node-mongo/API.md +654 -0
  75. package/.hive/src/lib/node-mongo/CHANGELOG.md +98 -0
  76. package/.hive/src/lib/node-mongo/README.md +97 -0
  77. package/.hive/src/lib/node-mongo/package-lock.json +3682 -0
  78. package/.hive/src/lib/node-mongo/package.json +74 -0
  79. package/.hive/src/lib/node-mongo/src/index.js +64 -0
  80. package/.hive/src/lib/node-mongo/src/mongo-query-service.js +78 -0
  81. package/.hive/src/lib/node-mongo/src/mongo-service-error.js +15 -0
  82. package/.hive/src/lib/node-mongo/src/mongo-service.js +303 -0
  83. package/.hive/src/logger.js +43 -0
  84. package/.hive/src/middlewares/allowNoAuth.js +9 -0
  85. package/.hive/src/middlewares/attachUser.js +41 -0
  86. package/.hive/src/middlewares/global/extractUserTokens.js +15 -0
  87. package/.hive/src/middlewares/global/tryToAttachUser.js +33 -0
  88. package/.hive/src/middlewares/isAuthorized.js +18 -0
  89. package/.hive/src/middlewares/shouldExist.js +37 -0
  90. package/.hive/src/middlewares/shouldNotExist.js +19 -0
  91. package/.hive/src/middlewares/uploadFile.js +5 -0
  92. package/.hive/src/middlewares/validate.js +32 -0
  93. package/.hive/src/migrations/migration.js +8 -0
  94. package/.hive/src/migrations/migration.service.js +73 -0
  95. package/.hive/src/migrations/migrations/1.js +22 -0
  96. package/.hive/src/migrations/migrations-log/migration-log.schema.js +13 -0
  97. package/.hive/src/migrations/migrations-log/migration-log.service.js +50 -0
  98. package/.hive/src/migrations/migrations.schema.js +6 -0
  99. package/.hive/src/migrations/migrator.js +75 -0
  100. package/.hive/src/migrator.js +4 -0
  101. package/.hive/src/resources/_dev/endpoints/triggerSchedulerHandler.js +32 -0
  102. package/.hive/src/resources/health/endpoints/get.js +19 -0
  103. package/.hive/src/resources/schemaMappings/schemaMappings.schema.js +6 -0
  104. package/.hive/src/resources/tokens/methods/generateSecureToken.js +9 -0
  105. package/.hive/src/resources/tokens/methods/setToken.js +8 -0
  106. package/.hive/src/resources/tokens/methods/storeToken.js +35 -0
  107. package/.hive/src/resources/tokens/tokens.schema.js +11 -0
  108. package/.hive/src/resources/users/endpoints/getCurrentUser.js +14 -0
  109. package/.hive/src/resources/users/endpoints/getUserProfile.js +19 -0
  110. package/.hive/src/resources/users/handlers/test.js +1 -0
  111. package/.hive/src/resources/users/methods/ensureUserCreated.js +68 -0
  112. package/.hive/src/resources/users/users.schema.js +16 -0
  113. package/.hive/src/routes/index.js +172 -0
  114. package/.hive/src/routes/middlewares/attachCustomErrors.js +28 -0
  115. package/.hive/src/routes/middlewares/routeErrorHandler.js +27 -0
  116. package/.hive/src/scheduler/handlers/sendDailyReport.example.js +7 -0
  117. package/.hive/src/scheduler.js +32 -0
  118. package/.hive/src/security.util.js +38 -0
  119. package/.hive/src/services/emailService.js +15 -0
  120. package/.hive/src/services/globalTest.js +0 -0
  121. package/.hive/src/services/setCookie.js +21 -0
  122. package/.hive/src/socketIo.js +99 -0
  123. package/.hive/tsconfig.json +31 -0
  124. package/AGENTS.md +96 -0
  125. package/README.md +271 -0
  126. package/cli/helpers/docker.js +59 -0
  127. package/cli/helpers/envCheck.js +123 -0
  128. package/cli/helpers/findPort.js +32 -0
  129. package/cli/hive.js +155 -15
  130. package/package.json +1 -1
  131. package/starter/.cursor/commands/add-endpoint.md +262 -0
  132. package/starter/.cursor/commands/add-handler.md +137 -0
  133. package/starter/.cursor/commands/add-middleware.md +95 -0
  134. package/starter/.cursor/commands/add-resource.md +71 -0
  135. package/starter/.cursor/commands/add-scheduler.md +138 -0
  136. package/starter/.cursor/commands/add-service.md +188 -0
  137. package/starter/.cursor/skills/hive-auth/SKILL.md +134 -0
  138. package/starter/.cursor/skills/hive-database/SKILL.md +103 -0
  139. package/starter/.cursor/skills/hive-endpoint/SKILL.md +103 -0
  140. package/starter/.cursor/skills/hive-handler/SKILL.md +88 -0
  141. package/starter/.cursor/skills/hive-mapping/SKILL.md +85 -0
  142. package/starter/.cursor/skills/hive-middleware/SKILL.md +104 -0
  143. package/starter/.cursor/skills/hive-overview/SKILL.md +50 -0
  144. package/starter/.cursor/skills/hive-scheduler/SKILL.md +94 -0
  145. package/starter/.cursor/skills/hive-schema/SKILL.md +73 -0
  146. package/starter/.cursor/skills/hive-service/SKILL.md +90 -0
  147. package/starter/src/app.js +4 -3
  148. package/test-app/.cursor/commands/add-endpoint.md +262 -0
  149. package/test-app/.cursor/commands/add-handler.md +137 -0
  150. package/test-app/.cursor/commands/add-middleware.md +95 -0
  151. package/test-app/.cursor/commands/add-resource.md +71 -0
  152. package/test-app/.cursor/commands/add-scheduler.md +138 -0
  153. package/test-app/.cursor/commands/add-service.md +188 -0
  154. package/test-app/.cursor/skills/hive-auth/SKILL.md +134 -0
  155. package/test-app/.cursor/skills/hive-database/SKILL.md +103 -0
  156. package/test-app/.cursor/skills/hive-endpoint/SKILL.md +103 -0
  157. package/test-app/.cursor/skills/hive-handler/SKILL.md +88 -0
  158. package/test-app/.cursor/skills/hive-mapping/SKILL.md +85 -0
  159. package/test-app/.cursor/skills/hive-middleware/SKILL.md +104 -0
  160. package/test-app/.cursor/skills/hive-overview/SKILL.md +50 -0
  161. package/test-app/.cursor/skills/hive-scheduler/SKILL.md +94 -0
  162. package/test-app/.cursor/skills/hive-schema/SKILL.md +73 -0
  163. package/test-app/.cursor/skills/hive-service/SKILL.md +90 -0
  164. package/test-app/package-lock.json +8684 -0
  165. package/test-app/package.json +21 -0
package/cli/hive.js CHANGED
@@ -7,33 +7,171 @@ const inquirer = require('inquirer');
7
7
  const mergeDirs = require('./helpers/mergeDirs');
8
8
  const execCommand = require('./helpers/execCommand');
9
9
  const downloadDirectory = require('./helpers/downloadDirectory');
10
+ const { isDockerInstalled, startMongo, startRedis } = require('./helpers/docker');
11
+ const { ensureEnvConfig, hashCode } = require('./helpers/envCheck');
12
+ const { findAvailablePort } = require('./helpers/findPort');
10
13
  const axios = require('axios');
11
- const tsx = require('tsx/cjs/api')
14
+ const fs = require('fs');
15
+ const { spawn } = require('child_process');
16
+
17
+ const hivePackage = require('../package.json');
18
+
19
+ let appPackage = {
20
+ "name": "my-hive-app",
21
+ "version": "1.0.0",
22
+ "description": "Hive App",
23
+ "main": "src/app.js",
24
+ "scripts": {
25
+ "prepare": "hive prepare ./src",
26
+ "dev": "hive prepare ./src && HIVE_SRC=$PWD/src npm -C .hive run dev",
27
+ "start": "hive prepare ./src && HIVE_SRC=$PWD/src npm -C .hive run start",
28
+ "deploy-local": "hive build-local && hive deploy"
29
+ },
30
+ "keywords": [],
31
+ "author": "",
32
+ "license": "ISC",
33
+ "dependencies": {
34
+ "@paralect/hive": hivePackage.version,
35
+ },
36
+ "devDependencies": {
37
+ "nodemon": "^3.1.4"
38
+ }
39
+ }
40
+
41
+ program
42
+ .command('init [projectName]')
43
+ .description('Initialize a new Hive project')
44
+ .action(async (projectName) => {
45
+ try {
46
+ let name = projectName;
47
+
48
+ if (!name) {
49
+ const answer = await inquirer.prompt([
50
+ {
51
+ type: 'input',
52
+ name: 'projectName',
53
+ message: 'Project name:',
54
+ default: 'my-hive-app'
55
+ }
56
+ ]);
57
+ name = answer.projectName;
58
+ }
59
+
60
+ const projectDir = path.resolve(process.cwd(), name);
61
+
62
+ console.log(`\n🐝 Creating Hive project: ${name}\n`);
63
+
64
+ await execCommand(`mkdir -p ${projectDir}/src/resources`);
65
+ await execCommand(`mkdir -p ${projectDir}/src/middlewares`);
66
+ await execCommand(`mkdir -p ${projectDir}/src/services`);
67
+ await execCommand(`mkdir -p ${projectDir}/src/scheduler/handlers`);
68
+
69
+ fs.writeFileSync(
70
+ path.join(projectDir, 'package.json'),
71
+ JSON.stringify({
72
+ name,
73
+ ...appPackage
74
+
75
+ }, null, 2)
76
+ );
77
+
78
+ const redisDb = hashCode(name) % 16;
79
+ fs.writeFileSync(
80
+ path.join(projectDir, '.env'),
81
+ `MONGODB_URI=mongodb://localhost:27017/${name}\nREDIS_URI=redis://localhost:6379/${redisDb}\nPORT=3001\nNODE_ENV=development\n`
82
+ );
83
+
84
+ fs.writeFileSync(
85
+ path.join(projectDir, '.gitignore'),
86
+ `node_modules\n.hive\n.env\n`
87
+ );
88
+
89
+ await execCommand(`cp -r ${path.resolve(__dirname, '..')}/starter/.cursor ${projectDir}/`);
90
+
91
+ console.log(`✅ Project created!\n`);
92
+ console.log(`Next steps:\n`);
93
+ console.log(` cd ${name}`);
94
+ console.log(` hive run ./src\n`);
95
+ console.log(`Start building with AI commands:`);
96
+ console.log(` add-resource tasks`);
97
+ console.log(` add-endpoint tasks post title, status\n`);
98
+ } catch (error) {
99
+ console.error('An error occurred:', error.message);
100
+ }
101
+ });
102
+
103
+ program
104
+ .command('infra-docker')
105
+ .description('Start shared MongoDB and Redis containers for local development')
106
+ .action(async () => {
107
+ try {
108
+ if (!isDockerInstalled()) {
109
+ console.error('Docker is not installed or not running. Please install Docker first.');
110
+ process.exit(1);
111
+ }
112
+
113
+ console.log('\nStarting Hive infrastructure...\n');
114
+
115
+ await startMongo();
116
+ await startRedis();
117
+
118
+ console.log('\n✅ Infrastructure ready!\n');
119
+ console.log('Connection URIs:');
120
+ console.log(' MongoDB: mongodb://localhost:27017/<your-db-name>');
121
+ console.log(' Redis: redis://localhost:6379/<db-number>\n');
122
+ console.log('These containers are shared across all Hive projects.');
123
+ console.log('Each project uses a unique database name for isolation.\n');
124
+ } catch (error) {
125
+ console.error('An error occurred:', error.message);
126
+ }
127
+ });
12
128
 
13
129
  program
14
130
  .command('run [dirPath]')
15
131
  .description('Run Hive server')
16
132
  .action(async (dirPath = '.') => {
17
133
  try {
18
- process.env.HIVE_SRC = path.resolve(process.cwd(), dirPath);
19
-
20
- const hiveProjectDir = path.resolve(process.env.HIVE_SRC, `../.hive`);
134
+ const hiveSrc = path.resolve(process.cwd(), dirPath);
135
+ const projectDir = path.resolve(hiveSrc, '..');
136
+ const hiveProjectDir = path.resolve(projectDir, '.hive');
137
+
138
+ // Ensure env config exists
139
+ await ensureEnvConfig(projectDir);
140
+
141
+ // Find available port
142
+ const defaultPort = parseInt(process.env.PORT) || 3001;
143
+ const port = await findAvailablePort(defaultPort);
144
+ if (port !== defaultPort) {
145
+ console.log(` Port ${defaultPort} in use, using ${port} instead\n`);
146
+ }
147
+
21
148
  await execCommand(`mkdir -p ${hiveProjectDir}`);
22
149
  await execCommand(`cp -r ${path.resolve(__dirname, '..')}/starter/ ${hiveProjectDir}`);
23
150
 
24
- tsx.require(`${hiveProjectDir}/src/app.js`, __filename); // TSX doesn't work inside node_moduels
151
+ const child = spawn('npx', ['tsx', '--watch', 'src/app.js'], {
152
+ cwd: hiveProjectDir,
153
+ stdio: 'inherit',
154
+ env: { ...process.env, HIVE_SRC: hiveSrc, PORT: String(port) }
155
+ });
156
+
157
+ child.on('error', (err) => console.error('Failed to start:', err.message));
158
+ child.on('close', (code) => process.exit(code));
25
159
  } catch (error) {
26
160
  console.error('An error occurred:', error.message);
27
161
  }
28
162
  });
29
163
 
30
- program
164
+ program
31
165
  .command('prepare [dirPath]')
32
166
  .description('Prepare Hive server')
33
167
  .action(async (dirPath = '.') => {
34
168
  try {
35
169
  process.env.HIVE_SRC = path.resolve(process.cwd(), dirPath);
36
- const hiveProjectDir = path.resolve(process.env.HIVE_SRC, `../.hive`);
170
+ const projectDir = path.resolve(process.env.HIVE_SRC, '..');
171
+ const hiveProjectDir = path.resolve(projectDir, '.hive');
172
+
173
+ // Ensure env config exists
174
+ await ensureEnvConfig(projectDir);
37
175
 
38
176
  await execCommand(`mkdir -p ${hiveProjectDir}`);
39
177
  await execCommand(`cp -r ${path.resolve(__dirname, '..')}/starter/. ${hiveProjectDir}`);
@@ -42,7 +180,7 @@ program
42
180
  }
43
181
  });
44
182
 
45
- program
183
+ program
46
184
  .command('deploy')
47
185
  .description('Build hive project')
48
186
  .action(async () => {
@@ -50,7 +188,7 @@ program
50
188
  let outDir = path.resolve(process.cwd(), './dist');
51
189
  await mergeDirs({ hiveSrc: path.resolve(process.cwd()), outDir });
52
190
 
53
- console.log('outDir', path.resolve(outDir, `./deploy/script`))
191
+ console.log('outDir', path.resolve(outDir, `./deploy/script`))
54
192
  await execCommand(`npm install --prefix ${path.resolve(outDir, `./deploy/script`)}`);
55
193
 
56
194
  await execCommand('node ./index.js', {
@@ -67,7 +205,7 @@ program
67
205
  .action(async (plugin) => {
68
206
  try {
69
207
  const destDir = process.cwd();
70
-
208
+
71
209
  await downloadDirectory(plugin);
72
210
  } catch (error) {
73
211
  console.error('An error occurred:', error.message);
@@ -80,12 +218,14 @@ program
80
218
  .description('Login into Hive Cloud')
81
219
  .action(async () => {
82
220
  if (process.env.HIVE_TOKEN) {
83
- const user = (await axios({ url: `https://hive-api-test.paralect.co/users/me`, method: 'get', headers: {
84
- 'Authorization': `Bearer ${process.env.HIVE_TOKEN}`
85
- } })).data;
221
+ const user = (await axios({
222
+ url: `https://hive-api-test.paralect.co/users/me`, method: 'get', headers: {
223
+ 'Authorization': `Bearer ${process.env.HIVE_TOKEN}`
224
+ }
225
+ })).data;
86
226
 
87
227
  console.log(`Already logged in!`, user);
88
-
228
+
89
229
  return;
90
230
  }
91
231
 
@@ -99,7 +239,7 @@ program
99
239
 
100
240
  try {
101
241
  await axios({ url: `https://hive-api-test.paralect.co/auth/login-code`, method: 'post', data: { email } });
102
-
242
+
103
243
  const { code } = await inquirer.prompt([
104
244
  {
105
245
  type: 'input',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paralect/hive",
3
- "version": "0.1.48",
3
+ "version": "0.1.50-alpha.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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