@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rhino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,427 @@
1
+ # Rhino — NestJS
2
+
3
+ > Automatic REST API generation for NestJS + Prisma with built-in security, validation, and advanced querying.
4
+
5
+ [![Node Version](https://img.shields.io/badge/node-18%2B-green)](https://nodejs.org/)
6
+ [![NestJS Version](https://img.shields.io/badge/nestjs-10%2B-red)](https://nestjs.com/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @rhino-dev/rhino-nestjs
13
+ ```
14
+
15
+ Then run the interactive installer:
16
+
17
+ ```bash
18
+ npx rhino install
19
+ ```
20
+
21
+ The installer walks you through:
22
+ - Connecting your Prisma client
23
+ - Enabling multi-tenancy
24
+ - Enabling audit trail
25
+ - Setting up Claude Code skills
26
+
27
+ ## Minimum Working Example
28
+
29
+ ### 1. Prisma Schema
30
+
31
+ ```prisma title="prisma/schema.prisma"
32
+ model Post {
33
+ id Int @id @default(autoincrement())
34
+ title String
35
+ content String?
36
+ status String @default("draft")
37
+ deletedAt DateTime?
38
+ createdAt DateTime @default(now())
39
+ updatedAt DateTime @updatedAt
40
+ organizationId Int?
41
+ organization Organization? @relation(fields: [organizationId], references: [id])
42
+ }
43
+
44
+ model Organization {
45
+ id Int @id @default(autoincrement())
46
+ name String
47
+ slug String @unique
48
+ posts Post[]
49
+ userRoles UserRole[]
50
+ auditLogs AuditLog[]
51
+ }
52
+
53
+ model UserRole {
54
+ id Int @id @default(autoincrement())
55
+ userId Int
56
+ organizationId Int
57
+ roleSlug String
58
+ permissions Json @default("[]")
59
+ organization Organization @relation(fields: [organizationId], references: [id])
60
+ }
61
+
62
+ model AuditLog {
63
+ id Int @id @default(autoincrement())
64
+ auditableType String
65
+ auditableId String
66
+ action String
67
+ oldValues Json?
68
+ newValues Json?
69
+ userId Int?
70
+ organizationId Int?
71
+ ipAddress String?
72
+ userAgent String?
73
+ createdAt DateTime @default(now())
74
+ organization Organization? @relation(fields: [organizationId], references: [id])
75
+ }
76
+ ```
77
+
78
+ ### 2. App Module
79
+
80
+ ```typescript title="src/app.module.ts"
81
+ import { Module } from '@nestjs/common';
82
+ import { RhinoModule } from '@rhino-dev/rhino-nestjs';
83
+ import { PrismaClient } from '@prisma/client';
84
+
85
+ const prisma = new PrismaClient();
86
+
87
+ @Module({
88
+ imports: [
89
+ RhinoModule.forRoot({
90
+ models: {
91
+ posts: {
92
+ model: 'post',
93
+ softDeletes: true,
94
+ belongsToOrganization: true,
95
+ hasAuditTrail: true,
96
+ allowedFilters: ['status'],
97
+ allowedSorts: ['createdAt', 'title'],
98
+ defaultSort: '-createdAt',
99
+ allowedIncludes: ['author'],
100
+ allowedSearch: ['title', 'content'],
101
+ },
102
+ },
103
+ routeGroups: {
104
+ tenant: {
105
+ prefix: ':organization',
106
+ models: '*',
107
+ },
108
+ },
109
+ multiTenant: {
110
+ organizationIdentifierColumn: 'slug',
111
+ },
112
+ auth: {
113
+ jwtSecret: process.env.JWT_SECRET,
114
+ },
115
+ }),
116
+ ],
117
+ })
118
+ export class AppModule {}
119
+ ```
120
+
121
+ > **Async variant:** `RhinoModule.forRootAsync({ useFactory: async (cfg: ConfigService) => ({...}), inject: [ConfigService] })`
122
+
123
+ ### 3. Bootstrap (main.ts)
124
+
125
+ ```typescript title="src/main.ts"
126
+ import { NestFactory } from '@nestjs/core';
127
+ import { AppModule } from './app.module';
128
+ import {
129
+ applyRhinoRouting,
130
+ } from '@rhino-dev/rhino-nestjs';
131
+
132
+ async function bootstrap() {
133
+ const app = await NestFactory.create(AppModule);
134
+ applyRhinoRouting(app, { prefix: 'api' });
135
+ await app.listen(3000);
136
+ }
137
+ bootstrap();
138
+ ```
139
+
140
+ That's it. You now have a full REST API for posts:
141
+
142
+ | Method | Endpoint | Description |
143
+ |--------|----------|-------------|
144
+ | `GET` | `/api/:organization/posts` | List with filters, sorts, search, pagination |
145
+ | `POST` | `/api/:organization/posts` | Create with validation |
146
+ | `GET` | `/api/:organization/posts/:id` | Show single record |
147
+ | `PUT` | `/api/:organization/posts/:id` | Update with validation |
148
+ | `DELETE` | `/api/:organization/posts/:id` | Soft delete |
149
+ | `GET` | `/api/:organization/posts/trashed` | List soft-deleted records |
150
+ | `POST` | `/api/:organization/posts/:id/restore` | Restore soft-deleted record |
151
+ | `DELETE` | `/api/:organization/posts/:id/force-delete` | Permanent delete |
152
+
153
+ ## Feature Summary
154
+
155
+ | # | Feature | Description |
156
+ |---|---------|-------------|
157
+ | 1 | **Automatic CRUD Endpoints** | Generates `index`, `show`, `store`, `update`, `destroy` for every registered model. |
158
+ | 2 | **Authentication** | Login, logout, password recovery/reset, invitation-based registration via JWT. |
159
+ | 3 | **Authorization & Policies** | Convention-based permission checks (`{slug}.{action}`), wildcard support. |
160
+ | 4 | **Role-Based Access Control** | Per-org roles via `user_roles` pivot table. |
161
+ | 5 | **Attribute-Level Permissions** | Control which fields each role can read and write. |
162
+ | 6 | **Validation** | Zod schemas with role-keyed rules. Store and update schemas are independent. |
163
+ | 7 | **Cross-Tenant FK Validation** | Organization-id is automatically stripped from user input; org always comes from middleware. |
164
+ | 8 | **Filtering** | `?filter[field]=value` with comma-separated OR values. |
165
+ | 9 | **Sorting** | `?sort=-createdAt,title` — ascending and descending, multiple fields. |
166
+ | 10 | **Full-Text Search** | `?search=term` across configured fields, supports relationship dot notation. |
167
+ | 11 | **Pagination** | Header-based metadata (`X-Current-Page`, `X-Last-Page`, `X-Per-Page`, `X-Total`). |
168
+ | 12 | **Field Selection** | `?fields[posts]=id,title,status` to reduce payload. |
169
+ | 13 | **Eager Loading** | `?include=author,comments` with nested dot-notation. |
170
+ | 14 | **Multi-Tenancy** | Organization-based data isolation, auto-set `organizationId`, request scoping. |
171
+ | 15 | **Nested Ownership** | Auto-scopes by `organizationId` on the registered model. |
172
+ | 16 | **Route Groups** | Multiple URL prefixes with different middleware/auth (`tenant`, `public`, custom). |
173
+ | 17 | **Soft Deletes** | Trash, restore, force-delete endpoints with individual permissions. |
174
+ | 18 | **Audit Trail** | Logs all CRUD events with old/new values, user, IP, and org context. |
175
+ | 19 | **Nested Operations** | `POST /nested` for atomic multi-model transactions with `$N.field` references. |
176
+ | 20 | **Invitations** | Token-based invite system with create, resend, cancel, accept, and role assignment. |
177
+ | 21 | **Hidden Columns** | Base + model-level + policy-level dynamic column hiding per role. |
178
+ | 22 | **Auto-Scope Discovery** | Custom `scopes` array per model registration; applies Prisma `where` additions. |
179
+ | 23 | **UUID Primary Keys** | `hasUuid: true` flag for string-based primary keys. |
180
+ | 24 | **Middleware Support** | Per-model `middleware` and per-action `actionMiddleware` arrays. |
181
+ | 25 | **Action Exclusion** | `exceptActions: ['destroy']` to disable specific CRUD routes. |
182
+ | 26 | **Generator CLI** | `rhino install`, `rhino generate`, `rhino blueprint`. |
183
+ | 27 | **Postman Export** | Auto-generated Postman Collection v2.1 with all endpoints. |
184
+ | 28 | **Blueprint System** | YAML-to-code generation for models, migrations, policies, tests, and seeders. |
185
+
186
+ ## Configuration Reference
187
+
188
+ `RhinoModule.forRoot(config: RhinoConfig)` accepts:
189
+
190
+ ### `models` (required)
191
+
192
+ A map of URL slug → model registration. The slug becomes the URL prefix and permission namespace.
193
+
194
+ ```typescript
195
+ models: {
196
+ // slug → ModelRegistration
197
+ posts: {
198
+ model: 'post', // Prisma model name (matches prisma client key)
199
+ policy: PostPolicy, // Optional: custom ResourcePolicy subclass
200
+ validation: PostSchema, // Zod schema for all writes (store + update)
201
+ validationStore: StoreSchema, // Zod schema for POST only (overrides validation)
202
+ validationUpdate: UpdateSchema, // Zod schema for PUT only (overrides validation)
203
+ allowedFilters: ['status', 'userId'],
204
+ allowedSorts: ['createdAt', 'title'],
205
+ defaultSort: '-createdAt',
206
+ allowedFields: ['id', 'title', 'status'],
207
+ allowedIncludes: ['author', 'comments'],
208
+ allowedSearch: ['title', 'content'],
209
+ paginationEnabled: true, // default: true
210
+ perPage: 25, // default: 25
211
+ softDeletes: true, // enables trash/restore/force-delete endpoints
212
+ belongsToOrganization: true, // scopes queries to current org
213
+ hasAuditTrail: true, // enables automatic change logging
214
+ hasUuid: false, // use string UUIDs instead of int IDs
215
+ additionalHiddenColumns: ['internalNotes'],
216
+ auditExclude: ['password'],
217
+ exceptActions: ['destroy'], // disable DELETE endpoint
218
+ middleware: [ThrottleMiddleware],
219
+ actionMiddleware: { store: [VerifiedMiddleware] },
220
+ owner: 'userId', // parent FK field for nested ownership chains
221
+ scopes: [PublishedScope], // custom Prisma scope classes
222
+ },
223
+ }
224
+ ```
225
+
226
+ ### `routeGroups`
227
+
228
+ ```typescript
229
+ routeGroups: {
230
+ tenant: {
231
+ prefix: ':organization', // URL prefix — :organization is the param name
232
+ middleware: [ResolveOrganizationMiddleware],
233
+ models: '*', // '*' or array of slugs: ['posts', 'comments']
234
+ skipAuth: false, // true = skip JWT guard (use for public groups)
235
+ },
236
+ public: {
237
+ prefix: 'public',
238
+ models: ['posts'],
239
+ skipAuth: true,
240
+ },
241
+ }
242
+ ```
243
+
244
+ **Reserved group names:**
245
+
246
+ | Name | Behavior |
247
+ |------|----------|
248
+ | `tenant` | Invitation and nested operation routes are registered under this prefix |
249
+ | `public` | `skipAuth: true` is implied if set on the group |
250
+
251
+ ### `multiTenant`
252
+
253
+ ```typescript
254
+ multiTenant: {
255
+ enabled: true,
256
+ organizationIdentifierColumn: 'slug', // 'id' | 'slug' | 'uuid' | any string
257
+ organizationModel: 'organization', // Prisma model name
258
+ userOrganizationModel: 'userRole', // Prisma model name
259
+ }
260
+ ```
261
+
262
+ ### `auth`
263
+
264
+ ```typescript
265
+ auth: {
266
+ jwtSecret: process.env.JWT_SECRET,
267
+ jwtExpiresIn: '7d',
268
+ userModel: 'user', // Prisma model for users
269
+ emailField: 'email',
270
+ passwordField: 'password',
271
+ }
272
+ ```
273
+
274
+ ### `nested`
275
+
276
+ ```typescript
277
+ nested: {
278
+ path: 'nested', // POST /{prefix}/nested
279
+ maxOperations: 50,
280
+ allowedModels: null, // null = all registered models
281
+ }
282
+ ```
283
+
284
+ ### `invitations`
285
+
286
+ ```typescript
287
+ invitations: {
288
+ expiresDays: 7,
289
+ allowedRoles: null, // null = all roles
290
+ notificationHandler: async (invitation) => { /* send email */ },
291
+ }
292
+ ```
293
+
294
+ ## Authorization Model
295
+
296
+ Permissions follow `{slug}.{action}` dot notation:
297
+
298
+ ```
299
+ posts.index — list posts
300
+ posts.show — view a single post
301
+ posts.store — create a post
302
+ posts.update — update a post
303
+ posts.destroy — soft delete a post
304
+ posts.trashed — view trash
305
+ posts.restore — restore from trash
306
+ posts.forceDelete — permanently delete
307
+ ```
308
+
309
+ **Wildcard support:**
310
+
311
+ | Permission | Meaning |
312
+ |------------|---------|
313
+ | `*` | Full access to everything |
314
+ | `posts.*` | All actions on posts |
315
+ | `posts.index` | Exact match — list only |
316
+
317
+ **Storage:**
318
+ - Non-tenant routes: `users.permissions` (JSON column)
319
+ - Tenant routes: `user_roles.permissions` (JSON column, scoped per org)
320
+
321
+ ## Query String Conventions
322
+
323
+ ```bash
324
+ # Filtering — AND by default; comma-separated values are OR
325
+ GET /api/org/posts?filter[status]=published
326
+ GET /api/org/posts?filter[status]=draft,published
327
+
328
+ # Sorting — prefix with - for descending; comma-separate for multiple
329
+ GET /api/org/posts?sort=-createdAt
330
+ GET /api/org/posts?sort=status,-createdAt
331
+
332
+ # Full-text search
333
+ GET /api/org/posts?search=laravel
334
+
335
+ # Pagination
336
+ GET /api/org/posts?page=2&per_page=20
337
+
338
+ # Field selection
339
+ GET /api/org/posts?fields[posts]=id,title,status
340
+
341
+ # Eager loading — dot notation for nested
342
+ GET /api/org/posts?include=author,comments.user
343
+
344
+ # Combined
345
+ GET /api/org/posts?filter[status]=published&sort=-createdAt&include=author&page=1&per_page=20
346
+ ```
347
+
348
+ **Pagination response headers:**
349
+ ```
350
+ X-Current-Page: 2
351
+ X-Last-Page: 10
352
+ X-Per-Page: 20
353
+ X-Total: 195
354
+ ```
355
+
356
+ ## CLI Commands
357
+
358
+ ```bash
359
+ # Interactive installation wizard
360
+ npx rhino install
361
+
362
+ # Scaffold a single resource (model registration, Zod schema, policy, test)
363
+ npx rhino generate
364
+
365
+ # Generate all models from YAML blueprints
366
+ npx rhino blueprint
367
+
368
+ # Generate a single model from a blueprint
369
+ npx rhino blueprint --model=posts
370
+
371
+ # Dry-run — preview without writing files
372
+ npx rhino blueprint --dry-run
373
+
374
+ # Force regeneration of unchanged blueprints
375
+ npx rhino blueprint --force
376
+
377
+ # Export Postman Collection v2.1
378
+ npx rhino export-postman
379
+ ```
380
+
381
+ ## Blueprint Quickstart
382
+
383
+ Blueprints generate models, migrations, policies, tests, and seeders from a YAML spec file. No tokens consumed.
384
+
385
+ ```yaml title=".rhino/blueprints/posts.yaml"
386
+ model: Post
387
+ slug: posts
388
+
389
+ options:
390
+ belongs_to_organization: true
391
+ soft_deletes: true
392
+ audit_trail: true
393
+
394
+ columns:
395
+ title:
396
+ type: string
397
+ filterable: true
398
+ sortable: true
399
+ searchable: true
400
+ content:
401
+ type: text
402
+ nullable: true
403
+ status:
404
+ type: string
405
+ default: "draft"
406
+ filterable: true
407
+
408
+ permissions:
409
+ admin:
410
+ actions: [index, show, store, update, destroy]
411
+ show_fields: "*"
412
+ create_fields: [title, content, status]
413
+ update_fields: [title, content, status]
414
+ viewer:
415
+ actions: [index, show]
416
+ show_fields: [id, title, status]
417
+ create_fields: []
418
+ update_fields: []
419
+ ```
420
+
421
+ ```bash
422
+ npx rhino blueprint
423
+ ```
424
+
425
+ ## License
426
+
427
+ MIT — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,130 @@
1
+ export interface BlueprintOptions {
2
+ belongs_to_organization: boolean;
3
+ soft_deletes: boolean;
4
+ audit_trail: boolean;
5
+ /** When true, primary key is `String @default(uuid())` instead of `Int @default(autoincrement())`. */
6
+ has_uuid: boolean;
7
+ /**
8
+ * BP-004: legacy single-hop owner. Kept for backwards compat; prefer
9
+ * `owner_chain` for multi-hop indirect tenancy.
10
+ */
11
+ owner: string | null;
12
+ /**
13
+ * BP-004: dot-notated chain from this model to its tenant-scoped ancestor.
14
+ * Example for Comment → Task → Project → Organization:
15
+ * owner_chain: task.project
16
+ * The last segment must name a model with `belongs_to_organization: true`.
17
+ */
18
+ owner_chain: string | null;
19
+ except_actions: string[];
20
+ pagination: boolean;
21
+ per_page: number;
22
+ }
23
+ /**
24
+ * BP-004: declares an FK column that must resolve to a record inside the
25
+ * current organization. Consumed by ValidationService.verifyTenantFks.
26
+ */
27
+ export interface BlueprintFkConstraint {
28
+ field: string;
29
+ model: string;
30
+ }
31
+ export interface BlueprintColumn {
32
+ name: string;
33
+ type: string;
34
+ nullable: boolean;
35
+ unique: boolean;
36
+ index: boolean;
37
+ default: unknown;
38
+ filterable: boolean;
39
+ sortable: boolean;
40
+ searchable: boolean;
41
+ precision: number | null;
42
+ scale: number | null;
43
+ foreignModel: string | null;
44
+ /** Enum values — only meaningful when type === 'enum' */
45
+ values?: string[];
46
+ }
47
+ export interface BlueprintPermission {
48
+ actions: string[];
49
+ show_fields: string[];
50
+ create_fields: Record<string, string> | string[];
51
+ update_fields: Record<string, string> | string[];
52
+ hidden_fields: string[];
53
+ }
54
+ export interface BlueprintRelationship {
55
+ name: string;
56
+ type: string;
57
+ model: string;
58
+ }
59
+ export interface Blueprint {
60
+ model: string;
61
+ slug: string;
62
+ table: string;
63
+ options: BlueprintOptions;
64
+ columns: BlueprintColumn[];
65
+ relationships: BlueprintRelationship[];
66
+ permissions: Record<string, BlueprintPermission>;
67
+ /** BP-004: explicit FK → model constraints for cross-tenant validation. */
68
+ fk_constraints?: BlueprintFkConstraint[];
69
+ source_file: string;
70
+ }
71
+ export declare class BlueprintParser {
72
+ /**
73
+ * Parse a model blueprint YAML file into a normalised Blueprint object.
74
+ * Throws on missing/invalid structure.
75
+ */
76
+ parseModel(filePath: string): Blueprint;
77
+ /**
78
+ * Compute SHA-256 hash of a file's content for manifest tracking.
79
+ */
80
+ computeFileHash(filePath: string): string;
81
+ private loadYaml;
82
+ private normalizeOptions;
83
+ /**
84
+ * Normalize various `owner_chain` YAML shapes into a single dot-notated
85
+ * string or null. Supports:
86
+ *
87
+ * owner_chain: task.project → 'task.project'
88
+ * owner_chain: [task, project] → 'task.project'
89
+ * owner_chain: null / absent → null
90
+ * owner_chain: '' → null
91
+ */
92
+ private normalizeOwnerChain;
93
+ /**
94
+ * Normalize the `fk_constraints:` YAML section into a flat array of
95
+ * { field, model } tuples. Accepts two shapes:
96
+ *
97
+ * fk_constraints:
98
+ * - field: projectId
99
+ * model: project
100
+ * - field: assignedTo
101
+ * model: user
102
+ *
103
+ * fk_constraints:
104
+ * projectId: project ← object shorthand
105
+ * assignedTo: user
106
+ */
107
+ private normalizeFkConstraints;
108
+ private normalizeColumns;
109
+ private normalizeRelationships;
110
+ private normalizePermissions;
111
+ /**
112
+ * Normalize show_fields: '*' → ['*'], array → array as-is.
113
+ */
114
+ private normalizeFieldList;
115
+ /**
116
+ * Normalize create_fields / update_fields.
117
+ * These can be either:
118
+ * { title: required, content: nullable } (object)
119
+ * [title, content] (array)
120
+ * '*' (wildcard string)
121
+ * We keep the object form as-is (a Record<string, string>) and convert
122
+ * arrays/wildcards to the same shape for uniform downstream handling.
123
+ */
124
+ private normalizeFieldMap;
125
+ /**
126
+ * Convert PascalCase model name to plural snake_case slug.
127
+ * e.g. BlogPost → blog_posts
128
+ */
129
+ private toSlug;
130
+ }