@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.
- package/.cursor/commands/deslop.md +12 -0
- 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/AGENTS.md +96 -0
- package/README.md +271 -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 +155 -15
- package/package.json +1 -1
- package/starter/.cursor/commands/add-endpoint.md +262 -0
- package/starter/.cursor/commands/add-handler.md +137 -0
- package/starter/.cursor/commands/add-middleware.md +95 -0
- package/starter/.cursor/commands/add-resource.md +71 -0
- package/starter/.cursor/commands/add-scheduler.md +138 -0
- package/starter/.cursor/commands/add-service.md +188 -0
- package/starter/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/starter/.cursor/skills/hive-database/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/starter/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/starter/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/starter/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/starter/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/starter/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/starter/.cursor/skills/hive-service/SKILL.md +90 -0
- package/starter/src/app.js +4 -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 +8684 -0
- 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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
const hiveProjectDir = path.resolve(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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',
|
|
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({
|
|
84
|
-
'
|
|
85
|
-
|
|
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
|
@@ -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
|