@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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +427 -0
  3. package/dist/blueprint/blueprint-parser.d.ts +130 -0
  4. package/dist/blueprint/blueprint-parser.js +276 -0
  5. package/dist/blueprint/blueprint-parser.js.map +1 -0
  6. package/dist/blueprint/blueprint-runner.d.ts +51 -0
  7. package/dist/blueprint/blueprint-runner.js +238 -0
  8. package/dist/blueprint/blueprint-runner.js.map +1 -0
  9. package/dist/blueprint/blueprint-validator.d.ts +19 -0
  10. package/dist/blueprint/blueprint-validator.js +163 -0
  11. package/dist/blueprint/blueprint-validator.js.map +1 -0
  12. package/dist/blueprint/generators/policy-generator.d.ts +40 -0
  13. package/dist/blueprint/generators/policy-generator.js +157 -0
  14. package/dist/blueprint/generators/policy-generator.js.map +1 -0
  15. package/dist/blueprint/generators/prisma-schema-generator.d.ts +43 -0
  16. package/dist/blueprint/generators/prisma-schema-generator.js +197 -0
  17. package/dist/blueprint/generators/prisma-schema-generator.js.map +1 -0
  18. package/dist/blueprint/generators/resource-definition-generator.d.ts +52 -0
  19. package/dist/blueprint/generators/resource-definition-generator.js +229 -0
  20. package/dist/blueprint/generators/resource-definition-generator.js.map +1 -0
  21. package/dist/blueprint/generators/seeder-generator.d.ts +21 -0
  22. package/dist/blueprint/generators/seeder-generator.js +135 -0
  23. package/dist/blueprint/generators/seeder-generator.js.map +1 -0
  24. package/dist/blueprint/generators/test-generator.d.ts +28 -0
  25. package/dist/blueprint/generators/test-generator.js +275 -0
  26. package/dist/blueprint/generators/test-generator.js.map +1 -0
  27. package/dist/blueprint/manifest-manager.d.ts +58 -0
  28. package/dist/blueprint/manifest-manager.js +137 -0
  29. package/dist/blueprint/manifest-manager.js.map +1 -0
  30. package/dist/cli/commands/blueprint.command.d.ts +16 -0
  31. package/dist/cli/commands/blueprint.command.js +35 -0
  32. package/dist/cli/commands/blueprint.command.js.map +1 -0
  33. package/dist/cli/commands/export-postman.command.d.ts +5 -0
  34. package/dist/cli/commands/export-postman.command.js +39 -0
  35. package/dist/cli/commands/export-postman.command.js.map +1 -0
  36. package/dist/cli/commands/export-types.command.d.ts +4 -0
  37. package/dist/cli/commands/export-types.command.js +36 -0
  38. package/dist/cli/commands/export-types.command.js.map +1 -0
  39. package/dist/cli/commands/generate.command.d.ts +6 -0
  40. package/dist/cli/commands/generate.command.js +212 -0
  41. package/dist/cli/commands/generate.command.js.map +1 -0
  42. package/dist/cli/commands/install.command.d.ts +8 -0
  43. package/dist/cli/commands/install.command.js +205 -0
  44. package/dist/cli/commands/install.command.js.map +1 -0
  45. package/dist/cli/index.d.ts +17 -0
  46. package/dist/cli/index.js +123 -0
  47. package/dist/cli/index.js.map +1 -0
  48. package/dist/cli/utils/io.d.ts +12 -0
  49. package/dist/cli/utils/io.js +74 -0
  50. package/dist/cli/utils/io.js.map +1 -0
  51. package/dist/cli/utils/prompt.d.ts +12 -0
  52. package/dist/cli/utils/prompt.js +78 -0
  53. package/dist/cli/utils/prompt.js.map +1 -0
  54. package/dist/constants/tokens.d.ts +3 -0
  55. package/dist/constants/tokens.js +7 -0
  56. package/dist/constants/tokens.js.map +1 -0
  57. package/dist/controllers/auth.controller.d.ts +59 -0
  58. package/dist/controllers/auth.controller.js +121 -0
  59. package/dist/controllers/auth.controller.js.map +1 -0
  60. package/dist/controllers/global.controller.d.ts +44 -0
  61. package/dist/controllers/global.controller.js +357 -0
  62. package/dist/controllers/global.controller.js.map +1 -0
  63. package/dist/controllers/invitation.controller.d.ts +30 -0
  64. package/dist/controllers/invitation.controller.js +111 -0
  65. package/dist/controllers/invitation.controller.js.map +1 -0
  66. package/dist/controllers/nested.controller.d.ts +12 -0
  67. package/dist/controllers/nested.controller.js +69 -0
  68. package/dist/controllers/nested.controller.js.map +1 -0
  69. package/dist/decorators/index.d.ts +33 -0
  70. package/dist/decorators/index.js +79 -0
  71. package/dist/decorators/index.js.map +1 -0
  72. package/dist/errors/rhino-exception.d.ts +24 -0
  73. package/dist/errors/rhino-exception.js +47 -0
  74. package/dist/errors/rhino-exception.js.map +1 -0
  75. package/dist/exporters/postman-exporter.d.ts +21 -0
  76. package/dist/exporters/postman-exporter.js +396 -0
  77. package/dist/exporters/postman-exporter.js.map +1 -0
  78. package/dist/exporters/typescript-exporter.d.ts +23 -0
  79. package/dist/exporters/typescript-exporter.js +129 -0
  80. package/dist/exporters/typescript-exporter.js.map +1 -0
  81. package/dist/guards/jwt-auth.guard.d.ts +16 -0
  82. package/dist/guards/jwt-auth.guard.js +61 -0
  83. package/dist/guards/jwt-auth.guard.js.map +1 -0
  84. package/dist/guards/resource-policy.guard.d.ts +12 -0
  85. package/dist/guards/resource-policy.guard.js +111 -0
  86. package/dist/guards/resource-policy.guard.js.map +1 -0
  87. package/dist/index.d.ts +60 -0
  88. package/dist/index.js +171 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/interceptors/hidden-columns.interceptor.d.ts +18 -0
  91. package/dist/interceptors/hidden-columns.interceptor.js +66 -0
  92. package/dist/interceptors/hidden-columns.interceptor.js.map +1 -0
  93. package/dist/interceptors/response.interceptor.d.ts +19 -0
  94. package/dist/interceptors/response.interceptor.js +47 -0
  95. package/dist/interceptors/response.interceptor.js.map +1 -0
  96. package/dist/interfaces/rhino-config.interface.d.ts +103 -0
  97. package/dist/interfaces/rhino-config.interface.js +3 -0
  98. package/dist/interfaces/rhino-config.interface.js.map +1 -0
  99. package/dist/interfaces/rhino-request.interface.d.ts +12 -0
  100. package/dist/interfaces/rhino-request.interface.js +3 -0
  101. package/dist/interfaces/rhino-request.interface.js.map +1 -0
  102. package/dist/middleware/resolve-organization.middleware.d.ts +15 -0
  103. package/dist/middleware/resolve-organization.middleware.js +38 -0
  104. package/dist/middleware/resolve-organization.middleware.js.map +1 -0
  105. package/dist/middleware/route-group.middleware.d.ts +17 -0
  106. package/dist/middleware/route-group.middleware.js +60 -0
  107. package/dist/middleware/route-group.middleware.js.map +1 -0
  108. package/dist/middleware/tenant-route-rewrite.d.ts +76 -0
  109. package/dist/middleware/tenant-route-rewrite.js +124 -0
  110. package/dist/middleware/tenant-route-rewrite.js.map +1 -0
  111. package/dist/policies/resource-policy.d.ts +25 -0
  112. package/dist/policies/resource-policy.js +77 -0
  113. package/dist/policies/resource-policy.js.map +1 -0
  114. package/dist/prisma/prisma-soft-delete.extension.d.ts +16 -0
  115. package/dist/prisma/prisma-soft-delete.extension.js +57 -0
  116. package/dist/prisma/prisma-soft-delete.extension.js.map +1 -0
  117. package/dist/prisma/prisma-uuid.extension.d.ts +9 -0
  118. package/dist/prisma/prisma-uuid.extension.js +28 -0
  119. package/dist/prisma/prisma-uuid.extension.js.map +1 -0
  120. package/dist/prisma/prisma.service.d.ts +38 -0
  121. package/dist/prisma/prisma.service.js +100 -0
  122. package/dist/prisma/prisma.service.js.map +1 -0
  123. package/dist/rhino.config.d.ts +43 -0
  124. package/dist/rhino.config.js +130 -0
  125. package/dist/rhino.config.js.map +1 -0
  126. package/dist/rhino.module.d.ts +61 -0
  127. package/dist/rhino.module.js +268 -0
  128. package/dist/rhino.module.js.map +1 -0
  129. package/dist/services/audit.service.d.ts +35 -0
  130. package/dist/services/audit.service.js +92 -0
  131. package/dist/services/audit.service.js.map +1 -0
  132. package/dist/services/auth.service.d.ts +33 -0
  133. package/dist/services/auth.service.js +157 -0
  134. package/dist/services/auth.service.js.map +1 -0
  135. package/dist/services/invitation.service.d.ts +37 -0
  136. package/dist/services/invitation.service.js +143 -0
  137. package/dist/services/invitation.service.js.map +1 -0
  138. package/dist/services/nested.service.d.ts +34 -0
  139. package/dist/services/nested.service.js +163 -0
  140. package/dist/services/nested.service.js.map +1 -0
  141. package/dist/services/organization.service.d.ts +13 -0
  142. package/dist/services/organization.service.js +60 -0
  143. package/dist/services/organization.service.js.map +1 -0
  144. package/dist/services/query-builder.service.d.ts +31 -0
  145. package/dist/services/query-builder.service.js +151 -0
  146. package/dist/services/query-builder.service.js.map +1 -0
  147. package/dist/services/resource.service.d.ts +38 -0
  148. package/dist/services/resource.service.js +230 -0
  149. package/dist/services/resource.service.js.map +1 -0
  150. package/dist/services/route-registration.service.d.ts +40 -0
  151. package/dist/services/route-registration.service.js +68 -0
  152. package/dist/services/route-registration.service.js.map +1 -0
  153. package/dist/services/scope.service.d.ts +16 -0
  154. package/dist/services/scope.service.js +32 -0
  155. package/dist/services/scope.service.js.map +1 -0
  156. package/dist/services/serializer.service.d.ts +36 -0
  157. package/dist/services/serializer.service.js +90 -0
  158. package/dist/services/serializer.service.js.map +1 -0
  159. package/dist/services/validation.service.d.ts +50 -0
  160. package/dist/services/validation.service.js +203 -0
  161. package/dist/services/validation.service.js.map +1 -0
  162. package/dist/tsconfig.build.tsbuildinfo +1 -0
  163. package/dist/utils/fk-chain-walker.d.ts +30 -0
  164. package/dist/utils/fk-chain-walker.js +23 -0
  165. package/dist/utils/fk-chain-walker.js.map +1 -0
  166. package/dist/utils/format.d.ts +7 -0
  167. package/dist/utils/format.js +28 -0
  168. package/dist/utils/format.js.map +1 -0
  169. package/dist/utils/model-builder.d.ts +64 -0
  170. package/dist/utils/model-builder.js +67 -0
  171. package/dist/utils/model-builder.js.map +1 -0
  172. package/dist/utils/permission-matcher.d.ts +46 -0
  173. package/dist/utils/permission-matcher.js +120 -0
  174. package/dist/utils/permission-matcher.js.map +1 -0
  175. package/dist/utils/scope-discovery.d.ts +34 -0
  176. package/dist/utils/scope-discovery.js +102 -0
  177. package/dist/utils/scope-discovery.js.map +1 -0
  178. package/package.json +64 -0
  179. package/stubs/skills/rhino-audit.md +184 -0
  180. package/stubs/skills/rhino-bugfix.md +117 -0
  181. package/stubs/skills/rhino-deploy.md +5 -0
  182. package/stubs/skills/rhino-docs.md +147 -0
  183. package/stubs/skills/rhino-feature.md +146 -0
  184. package/stubs/skills/rhino-migrate.md +181 -0
  185. package/stubs/skills/rhino-model.md +180 -0
  186. package/stubs/skills/rhino-plan.md +5 -0
  187. package/stubs/skills/rhino-policy.md +183 -0
  188. package/stubs/skills/rhino-refactor.md +147 -0
  189. package/stubs/skills/rhino-review.md +113 -0
  190. package/stubs/skills/rhino-scope.md +140 -0
  191. 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