@rhino-dev/rhino-nestjs 0.2.5
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/LICENSE +21 -0
- package/README.md +427 -0
- package/dist/blueprint/blueprint-parser.d.ts +130 -0
- package/dist/blueprint/blueprint-parser.js +276 -0
- package/dist/blueprint/blueprint-parser.js.map +1 -0
- package/dist/blueprint/blueprint-runner.d.ts +51 -0
- package/dist/blueprint/blueprint-runner.js +238 -0
- package/dist/blueprint/blueprint-runner.js.map +1 -0
- package/dist/blueprint/blueprint-validator.d.ts +19 -0
- package/dist/blueprint/blueprint-validator.js +163 -0
- package/dist/blueprint/blueprint-validator.js.map +1 -0
- package/dist/blueprint/generators/policy-generator.d.ts +40 -0
- package/dist/blueprint/generators/policy-generator.js +157 -0
- package/dist/blueprint/generators/policy-generator.js.map +1 -0
- package/dist/blueprint/generators/prisma-schema-generator.d.ts +43 -0
- package/dist/blueprint/generators/prisma-schema-generator.js +197 -0
- package/dist/blueprint/generators/prisma-schema-generator.js.map +1 -0
- package/dist/blueprint/generators/resource-definition-generator.d.ts +52 -0
- package/dist/blueprint/generators/resource-definition-generator.js +229 -0
- package/dist/blueprint/generators/resource-definition-generator.js.map +1 -0
- package/dist/blueprint/generators/seeder-generator.d.ts +21 -0
- package/dist/blueprint/generators/seeder-generator.js +135 -0
- package/dist/blueprint/generators/seeder-generator.js.map +1 -0
- package/dist/blueprint/generators/test-generator.d.ts +28 -0
- package/dist/blueprint/generators/test-generator.js +275 -0
- package/dist/blueprint/generators/test-generator.js.map +1 -0
- package/dist/blueprint/manifest-manager.d.ts +58 -0
- package/dist/blueprint/manifest-manager.js +137 -0
- package/dist/blueprint/manifest-manager.js.map +1 -0
- package/dist/cli/commands/blueprint.command.d.ts +16 -0
- package/dist/cli/commands/blueprint.command.js +35 -0
- package/dist/cli/commands/blueprint.command.js.map +1 -0
- package/dist/cli/commands/export-postman.command.d.ts +5 -0
- package/dist/cli/commands/export-postman.command.js +39 -0
- package/dist/cli/commands/export-postman.command.js.map +1 -0
- package/dist/cli/commands/export-types.command.d.ts +4 -0
- package/dist/cli/commands/export-types.command.js +36 -0
- package/dist/cli/commands/export-types.command.js.map +1 -0
- package/dist/cli/commands/generate.command.d.ts +6 -0
- package/dist/cli/commands/generate.command.js +212 -0
- package/dist/cli/commands/generate.command.js.map +1 -0
- package/dist/cli/commands/install.command.d.ts +8 -0
- package/dist/cli/commands/install.command.js +205 -0
- package/dist/cli/commands/install.command.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.js +123 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/io.d.ts +12 -0
- package/dist/cli/utils/io.js +74 -0
- package/dist/cli/utils/io.js.map +1 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +78 -0
- package/dist/cli/utils/prompt.js.map +1 -0
- package/dist/constants/tokens.d.ts +3 -0
- package/dist/constants/tokens.js +7 -0
- package/dist/constants/tokens.js.map +1 -0
- package/dist/controllers/auth.controller.d.ts +59 -0
- package/dist/controllers/auth.controller.js +121 -0
- package/dist/controllers/auth.controller.js.map +1 -0
- package/dist/controllers/global.controller.d.ts +44 -0
- package/dist/controllers/global.controller.js +357 -0
- package/dist/controllers/global.controller.js.map +1 -0
- package/dist/controllers/invitation.controller.d.ts +30 -0
- package/dist/controllers/invitation.controller.js +111 -0
- package/dist/controllers/invitation.controller.js.map +1 -0
- package/dist/controllers/nested.controller.d.ts +12 -0
- package/dist/controllers/nested.controller.js +69 -0
- package/dist/controllers/nested.controller.js.map +1 -0
- package/dist/decorators/index.d.ts +33 -0
- package/dist/decorators/index.js +79 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/errors/rhino-exception.d.ts +24 -0
- package/dist/errors/rhino-exception.js +47 -0
- package/dist/errors/rhino-exception.js.map +1 -0
- package/dist/exporters/postman-exporter.d.ts +21 -0
- package/dist/exporters/postman-exporter.js +396 -0
- package/dist/exporters/postman-exporter.js.map +1 -0
- package/dist/exporters/typescript-exporter.d.ts +23 -0
- package/dist/exporters/typescript-exporter.js +129 -0
- package/dist/exporters/typescript-exporter.js.map +1 -0
- package/dist/guards/jwt-auth.guard.d.ts +16 -0
- package/dist/guards/jwt-auth.guard.js +61 -0
- package/dist/guards/jwt-auth.guard.js.map +1 -0
- package/dist/guards/resource-policy.guard.d.ts +12 -0
- package/dist/guards/resource-policy.guard.js +111 -0
- package/dist/guards/resource-policy.guard.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +171 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/hidden-columns.interceptor.d.ts +18 -0
- package/dist/interceptors/hidden-columns.interceptor.js +66 -0
- package/dist/interceptors/hidden-columns.interceptor.js.map +1 -0
- package/dist/interceptors/response.interceptor.d.ts +19 -0
- package/dist/interceptors/response.interceptor.js +47 -0
- package/dist/interceptors/response.interceptor.js.map +1 -0
- package/dist/interfaces/rhino-config.interface.d.ts +103 -0
- package/dist/interfaces/rhino-config.interface.js +3 -0
- package/dist/interfaces/rhino-config.interface.js.map +1 -0
- package/dist/interfaces/rhino-request.interface.d.ts +12 -0
- package/dist/interfaces/rhino-request.interface.js +3 -0
- package/dist/interfaces/rhino-request.interface.js.map +1 -0
- package/dist/middleware/resolve-organization.middleware.d.ts +15 -0
- package/dist/middleware/resolve-organization.middleware.js +38 -0
- package/dist/middleware/resolve-organization.middleware.js.map +1 -0
- package/dist/middleware/route-group.middleware.d.ts +17 -0
- package/dist/middleware/route-group.middleware.js +60 -0
- package/dist/middleware/route-group.middleware.js.map +1 -0
- package/dist/middleware/tenant-route-rewrite.d.ts +76 -0
- package/dist/middleware/tenant-route-rewrite.js +124 -0
- package/dist/middleware/tenant-route-rewrite.js.map +1 -0
- package/dist/policies/resource-policy.d.ts +25 -0
- package/dist/policies/resource-policy.js +77 -0
- package/dist/policies/resource-policy.js.map +1 -0
- package/dist/prisma/prisma-soft-delete.extension.d.ts +16 -0
- package/dist/prisma/prisma-soft-delete.extension.js +57 -0
- package/dist/prisma/prisma-soft-delete.extension.js.map +1 -0
- package/dist/prisma/prisma-uuid.extension.d.ts +9 -0
- package/dist/prisma/prisma-uuid.extension.js +28 -0
- package/dist/prisma/prisma-uuid.extension.js.map +1 -0
- package/dist/prisma/prisma.service.d.ts +38 -0
- package/dist/prisma/prisma.service.js +100 -0
- package/dist/prisma/prisma.service.js.map +1 -0
- package/dist/rhino.config.d.ts +43 -0
- package/dist/rhino.config.js +130 -0
- package/dist/rhino.config.js.map +1 -0
- package/dist/rhino.module.d.ts +61 -0
- package/dist/rhino.module.js +268 -0
- package/dist/rhino.module.js.map +1 -0
- package/dist/services/audit.service.d.ts +35 -0
- package/dist/services/audit.service.js +92 -0
- package/dist/services/audit.service.js.map +1 -0
- package/dist/services/auth.service.d.ts +33 -0
- package/dist/services/auth.service.js +157 -0
- package/dist/services/auth.service.js.map +1 -0
- package/dist/services/invitation.service.d.ts +37 -0
- package/dist/services/invitation.service.js +143 -0
- package/dist/services/invitation.service.js.map +1 -0
- package/dist/services/nested.service.d.ts +34 -0
- package/dist/services/nested.service.js +163 -0
- package/dist/services/nested.service.js.map +1 -0
- package/dist/services/organization.service.d.ts +13 -0
- package/dist/services/organization.service.js +60 -0
- package/dist/services/organization.service.js.map +1 -0
- package/dist/services/query-builder.service.d.ts +31 -0
- package/dist/services/query-builder.service.js +151 -0
- package/dist/services/query-builder.service.js.map +1 -0
- package/dist/services/resource.service.d.ts +38 -0
- package/dist/services/resource.service.js +230 -0
- package/dist/services/resource.service.js.map +1 -0
- package/dist/services/route-registration.service.d.ts +40 -0
- package/dist/services/route-registration.service.js +68 -0
- package/dist/services/route-registration.service.js.map +1 -0
- package/dist/services/scope.service.d.ts +16 -0
- package/dist/services/scope.service.js +32 -0
- package/dist/services/scope.service.js.map +1 -0
- package/dist/services/serializer.service.d.ts +36 -0
- package/dist/services/serializer.service.js +90 -0
- package/dist/services/serializer.service.js.map +1 -0
- package/dist/services/validation.service.d.ts +50 -0
- package/dist/services/validation.service.js +203 -0
- package/dist/services/validation.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/fk-chain-walker.d.ts +30 -0
- package/dist/utils/fk-chain-walker.js +23 -0
- package/dist/utils/fk-chain-walker.js.map +1 -0
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/format.js +28 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/model-builder.d.ts +64 -0
- package/dist/utils/model-builder.js +67 -0
- package/dist/utils/model-builder.js.map +1 -0
- package/dist/utils/permission-matcher.d.ts +46 -0
- package/dist/utils/permission-matcher.js +120 -0
- package/dist/utils/permission-matcher.js.map +1 -0
- package/dist/utils/scope-discovery.d.ts +34 -0
- package/dist/utils/scope-discovery.js +102 -0
- package/dist/utils/scope-discovery.js.map +1 -0
- package/package.json +64 -0
- package/stubs/skills/rhino-audit.md +184 -0
- package/stubs/skills/rhino-bugfix.md +117 -0
- package/stubs/skills/rhino-deploy.md +5 -0
- package/stubs/skills/rhino-docs.md +147 -0
- package/stubs/skills/rhino-feature.md +146 -0
- package/stubs/skills/rhino-migrate.md +181 -0
- package/stubs/skills/rhino-model.md +180 -0
- package/stubs/skills/rhino-plan.md +5 -0
- package/stubs/skills/rhino-policy.md +183 -0
- package/stubs/skills/rhino-refactor.md +147 -0
- package/stubs/skills/rhino-review.md +113 -0
- package/stubs/skills/rhino-scope.md +140 -0
- package/stubs/skills/rhino-test.md +233 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rhino-bugfix
|
|
3
|
+
description: Fix a bug in an Rhino NestJS application using TDD — reproduce with a failing test, fix the root cause, verify.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are fixing a bug in an Rhino NestJS application. Always reproduce the bug with a failing test before touching production code.
|
|
7
|
+
|
|
8
|
+
## Step 1: Reproduce the Bug
|
|
9
|
+
|
|
10
|
+
Before writing a fix, write a test that demonstrates the bug. The test MUST fail with the current code.
|
|
11
|
+
|
|
12
|
+
Describe the bug as a test assertion:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
it('should [expected behavior] but [actual behavior]', async () => {
|
|
16
|
+
// Arrange: set up the exact conditions that trigger the bug
|
|
17
|
+
// Act: perform the action that causes the bug
|
|
18
|
+
// Assert: what SHOULD happen (not what currently happens)
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run the test to confirm it fails:
|
|
23
|
+
```bash
|
|
24
|
+
npm test -- --testPathPattern=your-spec
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If the test passes immediately, either the bug is already fixed or you have not reproduced the correct scenario. Do not proceed until the test fails.
|
|
28
|
+
|
|
29
|
+
## Step 2: Diagnose the Root Cause
|
|
30
|
+
|
|
31
|
+
Identify which layer the bug lives in:
|
|
32
|
+
|
|
33
|
+
| Layer | File | Common bugs |
|
|
34
|
+
|-------|------|-------------|
|
|
35
|
+
| Model registration | `app.module.ts` | Wrong Prisma model name, missing filter/include |
|
|
36
|
+
| Query builder | `query-builder.service.ts` | Filter/sort/include not applied correctly |
|
|
37
|
+
| Resource service | `resource.service.ts` | Missing org scope, wrong castId, null not handled |
|
|
38
|
+
| Validation | `validation.service.ts` | Wrong schema picked, forbidden fields logic wrong |
|
|
39
|
+
| Policy | `policies/[model].policy.ts` | Wrong permission, missing role branch |
|
|
40
|
+
| Policy guard | `guards/resource-policy.guard.ts` | Wrong action resolved from HTTP method/path |
|
|
41
|
+
| Serializer | `services/serializer.service.ts` | Hidden column not stripped, wrong field returned |
|
|
42
|
+
| Controller | `controllers/global.controller.ts` | Wrong status code, missing audit log call |
|
|
43
|
+
|
|
44
|
+
Read the relevant file(s) to understand the logic flow.
|
|
45
|
+
|
|
46
|
+
## Step 3: Common Bug Patterns
|
|
47
|
+
|
|
48
|
+
### Bug: 404 on a valid record (wrong org scope)
|
|
49
|
+
|
|
50
|
+
The model has `belongsToOrganization: true` but the record was created without `organizationId`, or the request uses the wrong org identifier.
|
|
51
|
+
|
|
52
|
+
Check:
|
|
53
|
+
- `orgFilter()` in `resource.service.ts` — is `organizationId` being set on create?
|
|
54
|
+
- `ResolveOrganizationMiddleware` — is the org resolved from the correct URL param?
|
|
55
|
+
- `multiTenant.organizationIdentifierColumn` — does it match the actual column in the DB?
|
|
56
|
+
|
|
57
|
+
### Bug: 403 that should be 200 (permission check wrong)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Debug: check what permission is being checked
|
|
61
|
+
# Add temporary log in resource-policy.guard.ts:
|
|
62
|
+
console.log('Checking permission:', `${modelSlug}.${action}`, 'user:', user?.permissions);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Verify:
|
|
66
|
+
- The user actually has the correct permission in `user_roles.permissions`.
|
|
67
|
+
- `resolveUserRoleSlug()` returns the correct role for the user+org combination.
|
|
68
|
+
- The `resourceSlug` on the policy matches the key in `RhinoModule.forRoot()`.
|
|
69
|
+
|
|
70
|
+
### Bug: `organizationId` appears in the response body
|
|
71
|
+
|
|
72
|
+
The serializer is not stripping `organizationId`. This leaks tenant information.
|
|
73
|
+
|
|
74
|
+
Check `serializer.service.ts` — is `organizationId` in `additionalHiddenColumns` or in the policy's `hiddenAttributesForShow()`?
|
|
75
|
+
|
|
76
|
+
### Bug: Validation passes fields that should be forbidden
|
|
77
|
+
|
|
78
|
+
Check `validation.service.ts` `resolvePermittedFields()` — is the policy's `permittedAttributesForCreate()` being called? Is `allowAllFields` (`permittedFields = ['*']`) being returned when it should not be?
|
|
79
|
+
|
|
80
|
+
### Bug: Soft-deleted records appear in normal listing
|
|
81
|
+
|
|
82
|
+
`resource.service.ts` `findAll()` should filter `where.deletedAt = null` unless `onlyTrashed` or `includeTrashed` is set. Check that `softDeletes: true` is set on the model registration.
|
|
83
|
+
|
|
84
|
+
### Bug: Prisma error "model not found"
|
|
85
|
+
|
|
86
|
+
`PrismaService.model(name)` tries: exact name → camelCase → lowercase. If your Prisma model is `BlogPost`, pass `model: 'blogPost'` (camelCase) in the registration.
|
|
87
|
+
|
|
88
|
+
## Step 4: Apply the Fix
|
|
89
|
+
|
|
90
|
+
Make the minimal change that fixes the bug. Do not refactor unrelated code in the same change.
|
|
91
|
+
|
|
92
|
+
Run the test that was failing:
|
|
93
|
+
```bash
|
|
94
|
+
npm test -- --testPathPattern=your-spec
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
It should now pass.
|
|
98
|
+
|
|
99
|
+
## Step 5: Run the Full Suite
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm test
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All previously passing tests must still pass. If a new test failure appears, it means the fix has a side effect — investigate before proceeding.
|
|
106
|
+
|
|
107
|
+
## Step 6: Add Regression Context
|
|
108
|
+
|
|
109
|
+
Add a comment above the test that explains why the test exists:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Regression: bug where belongsToOrganization was not applied on findAll,
|
|
113
|
+
// allowing cross-tenant data leakage. Fixed in resource.service.ts:orgFilter().
|
|
114
|
+
it('scopes posts to the current organization', async () => { /* ... */ });
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This ensures future contributors understand why the test must not be deleted.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# /rhino-deploy — Deployment Guide
|
|
2
|
+
|
|
3
|
+
Guide for deploying an Rhino NestJS application: set required env vars (DATABASE_URL, JWT_SECRET),
|
|
4
|
+
run `npx prisma migrate deploy`, build with `npm run build`, start with `node dist/main.js`,
|
|
5
|
+
configure reverse proxy, and verify health endpoint.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rhino-docs
|
|
3
|
+
description: Update documentation for an Rhino NestJS change — JSDoc, README, CLAUDE.md, and the Docusaurus docs site.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are updating documentation for a change to an Rhino NestJS application or to the Rhino NestJS library itself.
|
|
7
|
+
|
|
8
|
+
## Step 1: Identify What Changed
|
|
9
|
+
|
|
10
|
+
Read the diff or ask the user what changed:
|
|
11
|
+
- New feature added?
|
|
12
|
+
- Existing behavior modified?
|
|
13
|
+
- Config option added or renamed?
|
|
14
|
+
- New CLI command?
|
|
15
|
+
- Bug fixed that users might have been working around?
|
|
16
|
+
|
|
17
|
+
## Step 2: Determine Which Docs Need Updates
|
|
18
|
+
|
|
19
|
+
| What changed | Docs to update |
|
|
20
|
+
|---|---|
|
|
21
|
+
| New `ModelRegistration` property | `README.md` config reference, `docs/server/nestjs/models.md` |
|
|
22
|
+
| New query parameter | `README.md` query conventions, `docs/server/nestjs/querying.md` |
|
|
23
|
+
| New feature (any) | `README.md` feature table, `CLAUDE.md` feature table, relevant Docusaurus page |
|
|
24
|
+
| Authorization behavior | `docs/server/nestjs/policies.md` |
|
|
25
|
+
| Multi-tenant behavior | `docs/server/nestjs/multi-tenancy.md` |
|
|
26
|
+
| Route group config | `docs/server/nestjs/route-groups.md` |
|
|
27
|
+
| Soft delete behavior | `docs/server/nestjs/soft-deletes.md` |
|
|
28
|
+
| Audit trail | `docs/server/nestjs/audit-trail.md` |
|
|
29
|
+
| Nested operations | `docs/server/nestjs/nested-operations.md` |
|
|
30
|
+
| Prisma migration flow | `docs/server/nestjs/models.md` |
|
|
31
|
+
| CLI command | `README.md` CLI section, `docs/server/nestjs/getting-started.md` |
|
|
32
|
+
| Blueprint YAML format | `docs/server/nestjs/blueprint.md` |
|
|
33
|
+
| Validation/Zod | `docs/server/nestjs/validation.md` |
|
|
34
|
+
| Request lifecycle | `docs/server/nestjs/request-lifecycle.md` |
|
|
35
|
+
|
|
36
|
+
## Step 3: README.md Updates
|
|
37
|
+
|
|
38
|
+
For new features, add a row to the feature summary table (keep the `#` numbering sequential):
|
|
39
|
+
|
|
40
|
+
```markdown
|
|
41
|
+
| 29 | **New Feature Name** | One-sentence description of what it does. |
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For new `ModelRegistration` properties, add to the config reference section:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
| `newProperty` | `type` | Description of what it controls. |
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For new query parameters, add to the query string conventions section with a `curl` example.
|
|
51
|
+
|
|
52
|
+
## Step 4: CLAUDE.md Updates
|
|
53
|
+
|
|
54
|
+
If the feature table in `CLAUDE.md` is affected, add a row:
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
| 29 | **New Feature** | `new-service.ts`, `affected-controller.ts` |
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If a development rule needs to be updated (e.g., new naming convention), update the relevant rule section.
|
|
61
|
+
|
|
62
|
+
## Step 5: Docusaurus Docs
|
|
63
|
+
|
|
64
|
+
The docs live at `../docs/docs/server/nestjs/`. Each file has a frontmatter block:
|
|
65
|
+
|
|
66
|
+
```markdown
|
|
67
|
+
---
|
|
68
|
+
sidebar_position: N
|
|
69
|
+
title: Page Title
|
|
70
|
+
---
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When updating a page:
|
|
74
|
+
1. Find the correct page for the feature category.
|
|
75
|
+
2. Update or add code examples — all examples must use TypeScript/NestJS syntax, not PHP/Laravel.
|
|
76
|
+
3. Add `:::tip`, `:::info`, or `:::warning` callouts for important nuances.
|
|
77
|
+
4. If adding a new page, match the `sidebar_position` to slot it in the right order.
|
|
78
|
+
|
|
79
|
+
### Code Example Style
|
|
80
|
+
|
|
81
|
+
All code examples must follow NestJS conventions:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// ✓ Correct — TypeScript, Zod, Prisma
|
|
85
|
+
const PostSchema = z.object({
|
|
86
|
+
title: z.string().max(255),
|
|
87
|
+
status: z.enum(['draft', 'published']),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
posts: {
|
|
91
|
+
model: 'post',
|
|
92
|
+
validation: PostSchema,
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```php
|
|
97
|
+
// ✗ Wrong — do not use PHP/Laravel examples in NestJS docs
|
|
98
|
+
protected $validationRules = ['title' => 'string|max:255'];
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Docusaurus Admonitions
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
:::tip
|
|
105
|
+
Use `validationUpdate: schema.partial()` so all fields become optional on PUT requests.
|
|
106
|
+
:::
|
|
107
|
+
|
|
108
|
+
:::info
|
|
109
|
+
The `belongsToOrganization` flag works at the query level — it does not validate FK chains.
|
|
110
|
+
:::
|
|
111
|
+
|
|
112
|
+
:::warning
|
|
113
|
+
Never accept `organizationId` from the request body. Rhino strips it automatically.
|
|
114
|
+
:::
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Step 6: JSDoc in Source Code
|
|
118
|
+
|
|
119
|
+
For any changed service method, update or add JSDoc:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
/**
|
|
123
|
+
* Applies the registered policy's `permittedAttributesForCreate()` to strip
|
|
124
|
+
* forbidden fields and validates the remainder against the model's Zod schema.
|
|
125
|
+
*
|
|
126
|
+
* @throws never — returns a ValidationResult; caller decides how to respond.
|
|
127
|
+
*/
|
|
128
|
+
validateForAction<T>(data, reg, ctx): ValidationResult<T>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Step 7: Verify Docs Build (Optional)
|
|
132
|
+
|
|
133
|
+
If the Docusaurus site has a local dev server:
|
|
134
|
+
```bash
|
|
135
|
+
cd ../docs && npm run start
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Check that the page renders correctly and the sidebar order is right.
|
|
139
|
+
|
|
140
|
+
## Checklist
|
|
141
|
+
|
|
142
|
+
- [ ] Feature table in `README.md` updated
|
|
143
|
+
- [ ] Feature table in `CLAUDE.md` updated
|
|
144
|
+
- [ ] Config reference in `README.md` updated if new option added
|
|
145
|
+
- [ ] Relevant Docusaurus page(s) updated with NestJS code examples
|
|
146
|
+
- [ ] JSDoc on changed public methods updated
|
|
147
|
+
- [ ] No PHP/Laravel code examples in NestJS docs
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rhino-feature
|
|
3
|
+
description: Add a new feature to an Rhino NestJS application using TDD. Reads context, writes tests first, then implements.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are adding a new feature to an Rhino NestJS application. Follow the TDD flow: understand → plan → test → implement → verify.
|
|
7
|
+
|
|
8
|
+
## Step 1: Understand the Codebase
|
|
9
|
+
|
|
10
|
+
Read the following files to understand the project structure before writing any code:
|
|
11
|
+
|
|
12
|
+
1. `CLAUDE.md` — development rules, architecture overview, feature table.
|
|
13
|
+
2. `src/app.module.ts` — which models are registered and how.
|
|
14
|
+
3. `prisma/schema.prisma` — the data model.
|
|
15
|
+
4. Any existing `*.spec.ts` files related to the feature area.
|
|
16
|
+
|
|
17
|
+
Ask the user to clarify the feature if anything is ambiguous:
|
|
18
|
+
- Which model(s) does this feature affect?
|
|
19
|
+
- Which roles should have access?
|
|
20
|
+
- What are the expected HTTP status codes for happy path and error cases?
|
|
21
|
+
- Does it require a Prisma schema change?
|
|
22
|
+
|
|
23
|
+
## Step 2: Plan Before Coding
|
|
24
|
+
|
|
25
|
+
Write a brief plan in plain text (no code yet):
|
|
26
|
+
|
|
27
|
+
- What Prisma schema changes are needed?
|
|
28
|
+
- What `ModelRegistration` changes are needed (new `allowedFilters`, `allowedIncludes`, Zod schema, etc.)?
|
|
29
|
+
- What policy changes are needed (`permittedAttributesForCreate`, etc.)?
|
|
30
|
+
- What new service methods (if any) are needed?
|
|
31
|
+
- What tests will you write and what will they verify?
|
|
32
|
+
|
|
33
|
+
Show the plan to the user and wait for approval before proceeding.
|
|
34
|
+
|
|
35
|
+
## Step 3: Write Tests First
|
|
36
|
+
|
|
37
|
+
Create or update `*.spec.ts` files BEFORE touching implementation. Failing tests prove the feature is missing.
|
|
38
|
+
|
|
39
|
+
Write tests for each scenario:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Happy path — 200 / 201
|
|
43
|
+
it('allows admin to create a [resource]', async () => {
|
|
44
|
+
// Arrange: create org, user with admin role
|
|
45
|
+
// Act: POST /api/:org/resource with valid body
|
|
46
|
+
// Assert: 201, response matches expected shape
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Authorization denied — 403
|
|
50
|
+
it('blocks viewer from creating a [resource]', async () => {
|
|
51
|
+
// Arrange: user with viewer role (no store permission)
|
|
52
|
+
// Act: same POST request
|
|
53
|
+
// Assert: 403
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Validation failure — 422
|
|
57
|
+
it('returns 422 when required field is missing', async () => {
|
|
58
|
+
// Act: POST with empty body
|
|
59
|
+
// Assert: 422, errors object contains field key
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Not found — 404
|
|
63
|
+
it('returns 404 for unknown resource id', async () => { /* ... */ });
|
|
64
|
+
|
|
65
|
+
// Multi-tenant isolation
|
|
66
|
+
it('does not return [resource] data from another organization', async () => {
|
|
67
|
+
// Create data in org A, request from org B, verify 404 or empty list
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Run tests to confirm they fail:
|
|
72
|
+
```bash
|
|
73
|
+
npm test -- --testPathPattern=your-spec-file
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Step 4: Implement
|
|
77
|
+
|
|
78
|
+
Work in this order:
|
|
79
|
+
|
|
80
|
+
1. **Prisma schema** — add/modify models in `prisma/schema.prisma`, then:
|
|
81
|
+
```bash
|
|
82
|
+
npx prisma migrate dev --name add_[feature_name]
|
|
83
|
+
npx prisma generate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
2. **Zod schema** — create or update validation:
|
|
87
|
+
```typescript
|
|
88
|
+
// Base schema (used for both store and update)
|
|
89
|
+
export const PostSchema = z.object({
|
|
90
|
+
title: z.string().max(255),
|
|
91
|
+
status: z.enum(['draft', 'published']),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Action-specific override (update makes all fields optional)
|
|
95
|
+
export const PostUpdateSchema = PostSchema.partial();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
3. **Policy** — create or update a `ResourcePolicy` subclass:
|
|
99
|
+
```typescript
|
|
100
|
+
export class PostPolicy extends ResourcePolicy {
|
|
101
|
+
permittedAttributesForCreate(user: any): string[] {
|
|
102
|
+
if (this.hasRole(user, 'admin', /* org */)) return ['*'];
|
|
103
|
+
return ['title', 'content'];
|
|
104
|
+
}
|
|
105
|
+
permittedAttributesForUpdate(user: any): string[] {
|
|
106
|
+
return this.permittedAttributesForCreate(user);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
4. **Model registration** — update `RhinoModule.forRoot()`:
|
|
112
|
+
```typescript
|
|
113
|
+
posts: {
|
|
114
|
+
model: 'post',
|
|
115
|
+
policy: PostPolicy,
|
|
116
|
+
validation: PostSchema,
|
|
117
|
+
validationUpdate: PostUpdateSchema,
|
|
118
|
+
allowedFilters: ['status'],
|
|
119
|
+
allowedSorts: ['createdAt', 'title'],
|
|
120
|
+
softDeletes: true,
|
|
121
|
+
belongsToOrganization: true,
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
5. **Service methods** — add to an existing service if the logic is beyond standard CRUD. Only create a new controller if the feature cannot be expressed through model registration.
|
|
126
|
+
|
|
127
|
+
## Step 5: Verify
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm test -- --testPathPattern=your-spec-file # New tests must pass
|
|
131
|
+
npm test # Full suite must stay green
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Fix any regressions before declaring the feature done.
|
|
135
|
+
|
|
136
|
+
## Step 6: Update Docs
|
|
137
|
+
|
|
138
|
+
Update `docs/docs/server/nestjs/` — find the relevant page and update it with code examples. If this is a genuinely new concept, create a new doc page. Update `CLAUDE.md` if the features table changed.
|
|
139
|
+
|
|
140
|
+
## Common Mistakes to Avoid
|
|
141
|
+
|
|
142
|
+
- Do not include `organizationId` in the Zod schema — `ValidationService` strips it automatically.
|
|
143
|
+
- Do not use `class-validator` decorators — use Zod schemas.
|
|
144
|
+
- Do not create per-model controllers — all CRUD flows through `GlobalController`.
|
|
145
|
+
- Do not read Prisma directly from controllers — use `ResourceService`.
|
|
146
|
+
- Always test multi-tenant isolation, even if it seems obvious.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rhino-migrate
|
|
3
|
+
description: Create or update a Prisma migration in an Rhino NestJS project — schema change, registration update, validation update, and test run.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are making a Prisma schema change in an Rhino NestJS application. Follow these steps to keep the schema, registration, validation, and tests in sync.
|
|
7
|
+
|
|
8
|
+
## Step 1: Read Current State
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
cat prisma/schema.prisma
|
|
12
|
+
npm test # baseline — note pass/fail counts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also read:
|
|
16
|
+
- `src/app.module.ts` — model registrations affected by the schema change
|
|
17
|
+
- The Zod schema file(s) for affected models
|
|
18
|
+
- The policy file(s) for affected models
|
|
19
|
+
|
|
20
|
+
## Step 2: Make the Schema Change
|
|
21
|
+
|
|
22
|
+
Edit `prisma/schema.prisma`. Follow these conventions:
|
|
23
|
+
- Field names: camelCase (`organizationId`, `createdAt`)
|
|
24
|
+
- Model names: PascalCase singular (`BlogPost`)
|
|
25
|
+
- Required fields do NOT have `?`; optional fields have `?`
|
|
26
|
+
- Add `@default(now())` to `createdAt`, `@updatedAt` to `updatedAt`
|
|
27
|
+
- Add `deletedAt DateTime?` for soft-delete models
|
|
28
|
+
|
|
29
|
+
Common changes:
|
|
30
|
+
|
|
31
|
+
**Add a new field:**
|
|
32
|
+
```prisma
|
|
33
|
+
model Post {
|
|
34
|
+
// existing fields...
|
|
35
|
+
featuredImageUrl String? // new optional field
|
|
36
|
+
publishedAt DateTime? // new optional timestamp
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Add a new model:**
|
|
41
|
+
```prisma
|
|
42
|
+
model Tag {
|
|
43
|
+
id Int @id @default(autoincrement())
|
|
44
|
+
name String
|
|
45
|
+
slug String @unique
|
|
46
|
+
posts Post[] @relation("PostTags")
|
|
47
|
+
createdAt DateTime @default(now())
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Add a many-to-many relation:**
|
|
52
|
+
```prisma
|
|
53
|
+
model Post {
|
|
54
|
+
// ...
|
|
55
|
+
tags Tag[] @relation("PostTags")
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Add an index:**
|
|
60
|
+
```prisma
|
|
61
|
+
model Post {
|
|
62
|
+
organizationId Int
|
|
63
|
+
@@index([organizationId])
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Step 3: Run Migration
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Development (creates a new migration file)
|
|
71
|
+
npx prisma migrate dev --name [descriptive_name]
|
|
72
|
+
|
|
73
|
+
# Examples:
|
|
74
|
+
npx prisma migrate dev --name add_published_at_to_posts
|
|
75
|
+
npx prisma migrate dev --name create_tags_table
|
|
76
|
+
npx prisma migrate dev --name add_featured_image_url_to_posts
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then regenerate the Prisma client:
|
|
80
|
+
```bash
|
|
81
|
+
npx prisma generate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 4: Update Model Registration
|
|
85
|
+
|
|
86
|
+
In `app.module.ts`, update the affected model's `ModelRegistration`:
|
|
87
|
+
|
|
88
|
+
**New filterable/sortable fields:**
|
|
89
|
+
```typescript
|
|
90
|
+
posts: {
|
|
91
|
+
allowedFilters: ['status', 'userId', 'publishedAt'], // add publishedAt
|
|
92
|
+
allowedSorts: ['createdAt', 'title', 'publishedAt'], // add publishedAt
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**New includeable relationship:**
|
|
97
|
+
```typescript
|
|
98
|
+
posts: {
|
|
99
|
+
allowedIncludes: ['user', 'comments', 'tags'], // add tags
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**New searchable field:**
|
|
104
|
+
```typescript
|
|
105
|
+
posts: {
|
|
106
|
+
allowedSearch: ['title', 'content', 'excerpt'], // add excerpt
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Step 5: Update Zod Validation Schema
|
|
111
|
+
|
|
112
|
+
Add new fields to the validation schema(s). Match the Prisma types:
|
|
113
|
+
|
|
114
|
+
| Prisma type | Zod equivalent |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `String` | `z.string()` |
|
|
117
|
+
| `String?` | `z.string().optional()` |
|
|
118
|
+
| `Int` | `z.number().int()` |
|
|
119
|
+
| `Int?` | `z.number().int().optional()` |
|
|
120
|
+
| `Boolean` | `z.boolean()` |
|
|
121
|
+
| `DateTime` | `z.string().datetime()` or `z.date()` |
|
|
122
|
+
| `Json` | `z.any()` or a specific schema |
|
|
123
|
+
| `Float` / `Decimal` | `z.number()` |
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
export const PostSchema = z.object({
|
|
127
|
+
title: z.string().min(1).max(255),
|
|
128
|
+
content: z.string().optional(),
|
|
129
|
+
status: z.enum(['draft', 'published', 'archived']).optional(),
|
|
130
|
+
publishedAt: z.string().datetime().optional(), // new field
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Step 6: Update Policy (if needed)
|
|
135
|
+
|
|
136
|
+
If the new field should be write-restricted by role, update `permittedAttributesForCreate` and `permittedAttributesForUpdate`:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
permittedAttributesForCreate(user: any): string[] {
|
|
140
|
+
if (this.hasRole(user, 'admin')) return ['*'];
|
|
141
|
+
// Editors can set publishedAt only if they have the publish role
|
|
142
|
+
if (this.hasRole(user, 'publisher')) {
|
|
143
|
+
return ['title', 'content', 'publishedAt'];
|
|
144
|
+
}
|
|
145
|
+
return ['title', 'content'];
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Step 7: Run Tests
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm test
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Fix any failures. Common failure causes after a migration:
|
|
156
|
+
- Prisma client not regenerated (`npx prisma generate` again)
|
|
157
|
+
- Test database needs migration (`npx prisma migrate dev` in the test env)
|
|
158
|
+
- New required field not included in test seed data
|
|
159
|
+
- TypeScript type errors after schema change (`npx tsc --noEmit`)
|
|
160
|
+
|
|
161
|
+
## Step 8: Production Deployment Note
|
|
162
|
+
|
|
163
|
+
For production deployments, use `prisma migrate deploy` (not `migrate dev`):
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# In CI/CD pipeline — runs pending migrations without prompts
|
|
167
|
+
npx prisma migrate deploy
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Never run `migrate dev` in production. Document this in your deployment runbook.
|
|
171
|
+
|
|
172
|
+
## Checklist
|
|
173
|
+
|
|
174
|
+
- [ ] `prisma/schema.prisma` updated
|
|
175
|
+
- [ ] `npx prisma migrate dev --name [...]` run successfully
|
|
176
|
+
- [ ] `npx prisma generate` run to regenerate client
|
|
177
|
+
- [ ] `ModelRegistration` updated (filters, sorts, includes, search)
|
|
178
|
+
- [ ] Zod schema(s) updated with new fields
|
|
179
|
+
- [ ] Policy updated if field-level permissions needed
|
|
180
|
+
- [ ] All tests pass (`npm test`)
|
|
181
|
+
- [ ] `npx tsc --noEmit` produces no errors
|