@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
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
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://nestjs.com/)
|
|
7
|
+
[](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
|
+
}
|