@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,66 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.HiddenColumnsInterceptor = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const operators_1 = require("rxjs/operators");
15
+ const rhino_config_1 = require("../rhino.config");
16
+ const serializer_service_1 = require("../services/serializer.service");
17
+ /**
18
+ * Applies serializer filtering (base hidden, additional hidden, policy-driven
19
+ * whitelist/blacklist, computed attributes) when a non-serialized record is
20
+ * returned from a handler.
21
+ *
22
+ * Order with ResponseInterceptor: register this interceptor BEFORE
23
+ * ResponseInterceptor in the provider chain.
24
+ */
25
+ let HiddenColumnsInterceptor = class HiddenColumnsInterceptor {
26
+ config;
27
+ serializer;
28
+ constructor(config, serializer) {
29
+ this.config = config;
30
+ this.serializer = serializer;
31
+ }
32
+ intercept(context, next) {
33
+ const req = context.switchToHttp().getRequest();
34
+ const modelSlug = req.params?.modelSlug;
35
+ const reg = modelSlug ? this.config.model(modelSlug) : null;
36
+ if (!reg)
37
+ return next.handle();
38
+ // BP-007: pass {user, organization} so role-keyed policies resolve properly.
39
+ const ctx = { user: req.user, organization: req.organization };
40
+ return next.handle().pipe((0, operators_1.map)((body) => {
41
+ if (!body)
42
+ return body;
43
+ // Preserve paginated envelope shape if present
44
+ if (body.__rhinoPaginated) {
45
+ return {
46
+ ...body,
47
+ items: this.serializer.serializeMany(body.items, reg, ctx),
48
+ };
49
+ }
50
+ if (body.data && Array.isArray(body.data)) {
51
+ return { ...body, data: this.serializer.serializeMany(body.data, reg, ctx) };
52
+ }
53
+ if (Array.isArray(body)) {
54
+ return this.serializer.serializeMany(body, reg, ctx);
55
+ }
56
+ return this.serializer.serializeOne(body, reg, ctx);
57
+ }));
58
+ }
59
+ };
60
+ exports.HiddenColumnsInterceptor = HiddenColumnsInterceptor;
61
+ exports.HiddenColumnsInterceptor = HiddenColumnsInterceptor = __decorate([
62
+ (0, common_1.Injectable)(),
63
+ __metadata("design:paramtypes", [rhino_config_1.RhinoConfigService,
64
+ serializer_service_1.SerializerService])
65
+ ], HiddenColumnsInterceptor);
66
+ //# sourceMappingURL=hidden-columns.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hidden-columns.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/hidden-columns.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AAExB,8CAAqC;AACrC,kDAAqD;AACrD,uEAAmE;AAEnE;;;;;;;GAOG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IAEhB;IACA;IAFnB,YACmB,MAA0B,EAC1B,UAA6B;QAD7B,WAAM,GAAN,MAAM,CAAoB;QAC1B,eAAU,GAAV,UAAU,CAAmB;IAC7C,CAAC;IAEJ,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;QACxC,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5D,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QAC/B,6EAA6E;QAC7E,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QAE/D,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CACvB,IAAA,eAAG,EAAC,CAAC,IAAS,EAAE,EAAE;YAChB,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,+CAA+C;YAC/C,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,OAAO;oBACL,GAAG,IAAI;oBACP,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC;iBAC3D,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1C,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YAC/E,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,OAAO,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACvD,CAAC;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAA;AAlCY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;qCAGgB,iCAAkB;QACd,sCAAiB;GAHrC,wBAAwB,CAkCpC"}
@@ -0,0 +1,19 @@
1
+ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ export interface PaginatedShape {
4
+ __rhinoPaginated: true;
5
+ items: any[];
6
+ total: number;
7
+ page: number;
8
+ perPage: number;
9
+ lastPage: number;
10
+ }
11
+ export declare function paginated(items: any[], total: number, page: number, perPage: number): PaginatedShape;
12
+ /**
13
+ * Response envelope interceptor. Two output modes:
14
+ * - `paginated()` result → `{data: [...]}` + X-Current-Page/X-Last-Page/X-Per-Page/X-Total headers
15
+ * - anything else → pass-through (single record, 204, etc.)
16
+ */
17
+ export declare class ResponseInterceptor implements NestInterceptor {
18
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
19
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ResponseInterceptor = void 0;
10
+ exports.paginated = paginated;
11
+ const common_1 = require("@nestjs/common");
12
+ const operators_1 = require("rxjs/operators");
13
+ function paginated(items, total, page, perPage) {
14
+ return {
15
+ __rhinoPaginated: true,
16
+ items,
17
+ total,
18
+ page,
19
+ perPage,
20
+ lastPage: Math.max(1, Math.ceil(total / perPage)),
21
+ };
22
+ }
23
+ /**
24
+ * Response envelope interceptor. Two output modes:
25
+ * - `paginated()` result → `{data: [...]}` + X-Current-Page/X-Last-Page/X-Per-Page/X-Total headers
26
+ * - anything else → pass-through (single record, 204, etc.)
27
+ */
28
+ let ResponseInterceptor = class ResponseInterceptor {
29
+ intercept(context, next) {
30
+ const res = context.switchToHttp().getResponse();
31
+ return next.handle().pipe((0, operators_1.map)((body) => {
32
+ if (body && body.__rhinoPaginated) {
33
+ res.setHeader('X-Current-Page', body.page);
34
+ res.setHeader('X-Last-Page', body.lastPage);
35
+ res.setHeader('X-Per-Page', body.perPage);
36
+ res.setHeader('X-Total', body.total);
37
+ return { data: body.items };
38
+ }
39
+ return body;
40
+ }));
41
+ }
42
+ };
43
+ exports.ResponseInterceptor = ResponseInterceptor;
44
+ exports.ResponseInterceptor = ResponseInterceptor = __decorate([
45
+ (0, common_1.Injectable)()
46
+ ], ResponseInterceptor);
47
+ //# sourceMappingURL=response.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/response.interceptor.ts"],"names":[],"mappings":";;;;;;;;;AAkBA,8BASC;AA3BD,2CAKwB;AAExB,8CAAqC;AAWrC,SAAgB,SAAS,CAAC,KAAY,EAAE,KAAa,EAAE,IAAY,EAAE,OAAe;IAClF,OAAO;QACL,gBAAgB,EAAE,IAAI;QACtB,KAAK;QACL,KAAK;QACL,IAAI;QACJ,OAAO;QACP,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;KAClD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAC9B,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CACvB,IAAA,eAAG,EAAC,CAAC,IAAI,EAAE,EAAE;YACX,IAAI,IAAI,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAClC,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3C,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;gBAC5C,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC1C,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBACrC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;CACF,CAAA;AAhBY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;GACA,mBAAmB,CAgB/B"}
@@ -0,0 +1,103 @@
1
+ import type { Type } from '@nestjs/common';
2
+ import type { NestMiddleware } from '@nestjs/common';
3
+ import type { ZodSchema } from 'zod';
4
+ import type { ResourcePolicy } from '../policies/resource-policy';
5
+ import type { PrismaClientLike } from '../prisma/prisma.service';
6
+ export interface ModelRegistration {
7
+ /** Prisma model name (camelCase or PascalCase — matches the delegate on prisma client) */
8
+ model: string;
9
+ policy?: Type<ResourcePolicy>;
10
+ validation?: ZodSchema;
11
+ validationStore?: ZodSchema | Record<string, ZodSchema>;
12
+ validationUpdate?: ZodSchema | Record<string, ZodSchema>;
13
+ allowedFilters?: string[];
14
+ allowedSorts?: string[];
15
+ defaultSort?: string;
16
+ allowedFields?: string[];
17
+ allowedIncludes?: string[];
18
+ allowedSearch?: string[];
19
+ exceptActions?: string[];
20
+ paginationEnabled?: boolean;
21
+ perPage?: number;
22
+ softDeletes?: boolean;
23
+ middleware?: Type<NestMiddleware>[];
24
+ actionMiddleware?: Record<string, Type<NestMiddleware>[]>;
25
+ owner?: string;
26
+ belongsToOrganization?: boolean;
27
+ hasAuditTrail?: boolean;
28
+ hasUuid?: boolean;
29
+ additionalHiddenColumns?: string[];
30
+ auditExclude?: string[];
31
+ computedAttributes?: (record: any, user: any) => Record<string, any>;
32
+ scopes?: Type<any>[];
33
+ /** Foreign-key constraints to verify against the current organization. */
34
+ fkConstraints?: Array<{
35
+ field: string;
36
+ model: string;
37
+ }>;
38
+ }
39
+ export interface RouteGroupConfig {
40
+ prefix?: string;
41
+ middleware?: Type<NestMiddleware>[];
42
+ /** '*' = all registered models, array = subset by slug */
43
+ models: '*' | string[];
44
+ /** Skip the default JWT guard for this group (for public routes) */
45
+ skipAuth?: boolean;
46
+ }
47
+ export interface MultiTenantConfig {
48
+ enabled?: boolean;
49
+ organizationIdentifierColumn?: 'id' | 'slug' | 'uuid' | string;
50
+ organizationModel?: string;
51
+ userOrganizationModel?: string;
52
+ }
53
+ export interface NestedConfig {
54
+ path?: string;
55
+ maxOperations?: number;
56
+ allowedModels?: string[] | null;
57
+ }
58
+ export interface InvitationConfig {
59
+ expiresDays?: number;
60
+ allowedRoles?: string[] | null;
61
+ notificationHandler?: (invitation: any) => Promise<void> | void;
62
+ }
63
+ export interface AuthConfig {
64
+ jwtSecret?: string;
65
+ jwtExpiresIn?: string;
66
+ userModel?: string;
67
+ passwordField?: string;
68
+ emailField?: string;
69
+ }
70
+ export interface PostmanConfig {
71
+ roleModel?: string;
72
+ userRoleModel?: string;
73
+ userModel?: string;
74
+ }
75
+ export interface RhinoConfig {
76
+ /**
77
+ * The consuming app's PrismaClient instance. Optional at the interface level
78
+ * so tests can construct a config without Prisma, but `RhinoModule.forRoot`
79
+ * expects it in production use.
80
+ */
81
+ prismaClient?: PrismaClientLike;
82
+ models: Record<string, ModelRegistration>;
83
+ routeGroups?: Record<string, RouteGroupConfig>;
84
+ multiTenant?: MultiTenantConfig;
85
+ nested?: NestedConfig;
86
+ invitations?: InvitationConfig;
87
+ auth?: AuthConfig;
88
+ postman?: PostmanConfig;
89
+ clientPath?: string;
90
+ mobilePath?: string;
91
+ testFramework?: 'jest' | 'vitest';
92
+ }
93
+ export interface RhinoModuleAsyncOptions {
94
+ imports?: any[];
95
+ inject?: any[];
96
+ useFactory: (...args: any[]) => Promise<RhinoConfig> | RhinoConfig;
97
+ /**
98
+ * Middleware classes referenced in `models[*].middleware` / `actionMiddleware`.
99
+ * NestJS requires providers be declared synchronously, so for async config
100
+ * the user must list them here to enable auto-wiring.
101
+ */
102
+ middleware?: Type<NestMiddleware>[];
103
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=rhino-config.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rhino-config.interface.js","sourceRoot":"","sources":["../../src/interfaces/rhino-config.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Typed shape of the Express request after Rhino middleware has run.
4
+ * Internal keys are prefixed `__` to avoid collisions with app data.
5
+ */
6
+ export interface RhinoRequest<TUser = any, TOrg = any> extends Request {
7
+ user?: TUser;
8
+ organization?: TOrg;
9
+ __routeGroup?: string;
10
+ __skipAuth?: boolean;
11
+ __action?: string;
12
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=rhino-request.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rhino-request.interface.js","sourceRoot":"","sources":["../../src/interfaces/rhino-request.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import { NestMiddleware } from '@nestjs/common';
2
+ import { NextFunction, Request, Response } from 'express';
3
+ import { OrganizationService } from '../services/organization.service';
4
+ /**
5
+ * Resolves the `:organization` route parameter and attaches the org
6
+ * to `req.organization`. Ensures the authenticated user belongs to it.
7
+ */
8
+ export declare class ResolveOrganizationMiddleware implements NestMiddleware {
9
+ private readonly orgService;
10
+ constructor(orgService: OrganizationService);
11
+ use(req: Request & {
12
+ user?: any;
13
+ organization?: any;
14
+ }, _res: Response, next: NextFunction): Promise<void>;
15
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ResolveOrganizationMiddleware = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const organization_service_1 = require("../services/organization.service");
15
+ /**
16
+ * Resolves the `:organization` route parameter and attaches the org
17
+ * to `req.organization`. Ensures the authenticated user belongs to it.
18
+ */
19
+ let ResolveOrganizationMiddleware = class ResolveOrganizationMiddleware {
20
+ orgService;
21
+ constructor(orgService) {
22
+ this.orgService = orgService;
23
+ }
24
+ async use(req, _res, next) {
25
+ const identifier = req.params.organization ?? req.params.org;
26
+ if (!identifier)
27
+ return next();
28
+ const org = await this.orgService.resolve(identifier, req.user);
29
+ req.organization = org;
30
+ next();
31
+ }
32
+ };
33
+ exports.ResolveOrganizationMiddleware = ResolveOrganizationMiddleware;
34
+ exports.ResolveOrganizationMiddleware = ResolveOrganizationMiddleware = __decorate([
35
+ (0, common_1.Injectable)(),
36
+ __metadata("design:paramtypes", [organization_service_1.OrganizationService])
37
+ ], ResolveOrganizationMiddleware);
38
+ //# sourceMappingURL=resolve-organization.middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-organization.middleware.js","sourceRoot":"","sources":["../../src/middleware/resolve-organization.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4D;AAE5D,2EAAuE;AAEvE;;;GAGG;AAEI,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IACX;IAA7B,YAA6B,UAA+B;QAA/B,eAAU,GAAV,UAAU,CAAqB;IAAG,CAAC;IAEhE,KAAK,CAAC,GAAG,CAAC,GAAiD,EAAE,IAAc,EAAE,IAAkB;QAC7F,MAAM,UAAU,GAAI,GAAG,CAAC,MAAc,CAAC,YAAY,IAAK,GAAG,CAAC,MAAc,CAAC,GAAG,CAAC;QAC/E,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAChE,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC;QACvB,IAAI,EAAE,CAAC;IACT,CAAC;CACF,CAAA;AAVY,sEAA6B;wCAA7B,6BAA6B;IADzC,IAAA,mBAAU,GAAE;qCAE8B,0CAAmB;GADjD,6BAA6B,CAUzC"}
@@ -0,0 +1,17 @@
1
+ import { NestMiddleware } from '@nestjs/common';
2
+ import type { NextFunction, Request, Response } from 'express';
3
+ import { RhinoConfigService } from '../rhino.config';
4
+ /**
5
+ * Detects which Rhino route group the current request belongs to
6
+ * (based on URL prefix) and attaches:
7
+ * - `req.__routeGroup` — the group name
8
+ * - `req.__skipAuth` — true when the group has `skipAuth: true`
9
+ *
10
+ * Install this middleware globally BEFORE `JwtAuthGuard` so the guard can
11
+ * read `req.__skipAuth` and short-circuit.
12
+ */
13
+ export declare class RouteGroupMiddleware implements NestMiddleware {
14
+ private readonly config;
15
+ constructor(config: RhinoConfigService);
16
+ use(req: Request & Record<string, any>, _res: Response, next: NextFunction): void;
17
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.RouteGroupMiddleware = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const rhino_config_1 = require("../rhino.config");
15
+ /**
16
+ * Detects which Rhino route group the current request belongs to
17
+ * (based on URL prefix) and attaches:
18
+ * - `req.__routeGroup` — the group name
19
+ * - `req.__skipAuth` — true when the group has `skipAuth: true`
20
+ *
21
+ * Install this middleware globally BEFORE `JwtAuthGuard` so the guard can
22
+ * read `req.__skipAuth` and short-circuit.
23
+ */
24
+ let RouteGroupMiddleware = class RouteGroupMiddleware {
25
+ config;
26
+ constructor(config) {
27
+ this.config = config;
28
+ }
29
+ use(req, _res, next) {
30
+ // BP-006: `req.url` is stripped to the middleware's mount-point when
31
+ // registered via NestModule.configure(consumer).forRoutes('*'), so it
32
+ // collapses to `/` for every request. `req.originalUrl` carries the full
33
+ // request URL verbatim — use that for prefix matching.
34
+ const url = String(req.originalUrl ?? req.url ?? '').split('?')[0];
35
+ const groups = this.config.routeGroups();
36
+ let match = null;
37
+ for (const [name, group] of Object.entries(groups)) {
38
+ const prefix = group.prefix ?? '';
39
+ if (!prefix)
40
+ continue;
41
+ if (prefix.startsWith(':'))
42
+ continue; // dynamic tenant prefix — handled by ResolveOrganizationMiddleware
43
+ if (url.includes(`/${prefix}/`) || url.endsWith(`/${prefix}`)) {
44
+ match = { name, prefix };
45
+ if (group.skipAuth)
46
+ req.__skipAuth = true;
47
+ break;
48
+ }
49
+ }
50
+ if (match)
51
+ req.__routeGroup = match.name;
52
+ next();
53
+ }
54
+ };
55
+ exports.RouteGroupMiddleware = RouteGroupMiddleware;
56
+ exports.RouteGroupMiddleware = RouteGroupMiddleware = __decorate([
57
+ (0, common_1.Injectable)(),
58
+ __metadata("design:paramtypes", [rhino_config_1.RhinoConfigService])
59
+ ], RouteGroupMiddleware);
60
+ //# sourceMappingURL=route-group.middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group.middleware.js","sourceRoot":"","sources":["../../src/middleware/route-group.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4D;AAE5D,kDAAqD;AAErD;;;;;;;;GAQG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,GAAG,CAAC,GAAkC,EAAE,IAAc,EAAE,IAAkB;QACxE,qEAAqE;QACrE,sEAAsE;QACtE,yEAAyE;QACzE,uDAAuD;QACvD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,KAAK,GAA4C,IAAI,CAAC;QAC1D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS,CAAC,mEAAmE;YACzG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,CAAC;gBAC9D,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gBACzB,IAAI,KAAK,CAAC,QAAQ;oBAAE,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;gBAC1C,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,KAAK;YAAE,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC;QACzC,IAAI,EAAE,CAAC;IACT,CAAC;CACF,CAAA;AAxBY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAE0B,iCAAkB;GAD5C,oBAAoB,CAwBhC"}
@@ -0,0 +1,76 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { RhinoConfig } from '../interfaces/rhino-config.interface';
3
+ import type { PrismaClientLike } from '../prisma/prisma.service';
4
+ export interface TenantRouteRewriteOptions {
5
+ /**
6
+ * Global API prefix used by the consumer (matches
7
+ * `applyRhinoRouting({ prefix })`). Defaults to `'api'`.
8
+ */
9
+ apiPrefix?: string;
10
+ /**
11
+ * Path segments to treat as non-tenant even when they match an org slug
12
+ * (they sit at the api root). Defaults cover the library's built-in
13
+ * public/auth endpoints plus `nested`.
14
+ */
15
+ reservedSegments?: string[];
16
+ /**
17
+ * When `true` (default), a URL that looks tenant-shaped but whose first
18
+ * segment doesn't match any known organization → HTTP 404. When `false`
19
+ * the request passes through unchanged (GlobalController then treats the
20
+ * segment as a model slug and likely returns its own UNKNOWN_RESOURCE
21
+ * 404).
22
+ */
23
+ strict?: boolean;
24
+ /**
25
+ * When `true` (default), after resolving the org the middleware checks
26
+ * that the authenticated user (if any) is a member. Non-members produce
27
+ * a 404 (intentional — matches Laravel "Organization not found"). If
28
+ * `req.user` is absent the check is skipped (auth runs later).
29
+ */
30
+ enforceMembership?: boolean;
31
+ }
32
+ /**
33
+ * Factory for an Express-level middleware that implements the
34
+ * `routeGroups.tenant.prefix: ':organization'` contract promised by
35
+ * RhinoConfig.
36
+ *
37
+ * Context (BP-001): NestJS controllers are declared with static
38
+ * `@Controller(...)` paths, so the library cannot dynamically prefix them
39
+ * with a runtime `:organization` segment. NestModule.configure() middleware
40
+ * runs AFTER Nest has matched the handler, so a middleware registered there
41
+ * cannot rewrite the URL in time. This factory returns a handler intended
42
+ * for raw Express `app.use(tenantRouteRewrite(...))` BEFORE calling
43
+ * `applyRhinoRouting(...)`.
44
+ *
45
+ * Behavior:
46
+ *
47
+ * 1. Match `/{apiPrefix}/<slug>/<rest>`. If no match → next().
48
+ * 2. If `<slug>` is a reserved segment (auth/invitations/nested) → next().
49
+ * 3. Look up the organization by slug (with an in-process cache).
50
+ * - Not found + strict → 404 CROSS_TENANT
51
+ * - Not found + non-strict → next() (treated as a model slug)
52
+ * 4. When the request is authenticated, verify membership. Non-member →
53
+ * 404 CROSS_TENANT (same code as missing org — by design, no user
54
+ * enumeration).
55
+ * 5. Attach the resolved org to `req.organization` and rewrite
56
+ * `req.url` / `req.originalUrl` to drop the slug so the GlobalController
57
+ * continues to match `/{apiPrefix}/:modelSlug`.
58
+ *
59
+ * Usage:
60
+ *
61
+ * import { createTenantRouteRewrite, RHINO_CONFIG, RHINO_PRISMA_CLIENT } from '@rhino-dev/rhino-nestjs';
62
+ *
63
+ * async function bootstrap() {
64
+ * const app = await NestFactory.create(AppModule);
65
+ * const prisma = app.get(RHINO_PRISMA_CLIENT);
66
+ * const config = app.get(RHINO_CONFIG);
67
+ * app.use(createTenantRouteRewrite({ prisma, config }));
68
+ * applyRhinoRouting(app, { prefix: 'api' });
69
+ * await app.listen(8004);
70
+ * }
71
+ */
72
+ export declare function createTenantRouteRewrite(args: {
73
+ prisma: PrismaClientLike;
74
+ config: RhinoConfig;
75
+ options?: TenantRouteRewriteOptions;
76
+ }): (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTenantRouteRewrite = createTenantRouteRewrite;
4
+ /**
5
+ * Factory for an Express-level middleware that implements the
6
+ * `routeGroups.tenant.prefix: ':organization'` contract promised by
7
+ * RhinoConfig.
8
+ *
9
+ * Context (BP-001): NestJS controllers are declared with static
10
+ * `@Controller(...)` paths, so the library cannot dynamically prefix them
11
+ * with a runtime `:organization` segment. NestModule.configure() middleware
12
+ * runs AFTER Nest has matched the handler, so a middleware registered there
13
+ * cannot rewrite the URL in time. This factory returns a handler intended
14
+ * for raw Express `app.use(tenantRouteRewrite(...))` BEFORE calling
15
+ * `applyRhinoRouting(...)`.
16
+ *
17
+ * Behavior:
18
+ *
19
+ * 1. Match `/{apiPrefix}/<slug>/<rest>`. If no match → next().
20
+ * 2. If `<slug>` is a reserved segment (auth/invitations/nested) → next().
21
+ * 3. Look up the organization by slug (with an in-process cache).
22
+ * - Not found + strict → 404 CROSS_TENANT
23
+ * - Not found + non-strict → next() (treated as a model slug)
24
+ * 4. When the request is authenticated, verify membership. Non-member →
25
+ * 404 CROSS_TENANT (same code as missing org — by design, no user
26
+ * enumeration).
27
+ * 5. Attach the resolved org to `req.organization` and rewrite
28
+ * `req.url` / `req.originalUrl` to drop the slug so the GlobalController
29
+ * continues to match `/{apiPrefix}/:modelSlug`.
30
+ *
31
+ * Usage:
32
+ *
33
+ * import { createTenantRouteRewrite, RHINO_CONFIG, RHINO_PRISMA_CLIENT } from '@rhino-dev/rhino-nestjs';
34
+ *
35
+ * async function bootstrap() {
36
+ * const app = await NestFactory.create(AppModule);
37
+ * const prisma = app.get(RHINO_PRISMA_CLIENT);
38
+ * const config = app.get(RHINO_CONFIG);
39
+ * app.use(createTenantRouteRewrite({ prisma, config }));
40
+ * applyRhinoRouting(app, { prefix: 'api' });
41
+ * await app.listen(8004);
42
+ * }
43
+ */
44
+ function createTenantRouteRewrite(args) {
45
+ const { apiPrefix = 'api', reservedSegments = ['auth', 'invitations', 'nested'], strict = true, enforceMembership = true, } = args.options ?? {};
46
+ const reserved = new Set(reservedSegments);
47
+ const orgModel = args.config.multiTenant?.organizationModel ?? 'organization';
48
+ const userOrgModel = args.config.multiTenant?.userOrganizationModel ?? 'userRole';
49
+ const idColumn = args.config.multiTenant?.organizationIdentifierColumn ?? 'slug';
50
+ const knownModelSlugs = new Set(Object.keys(args.config.models ?? {}));
51
+ // Simple in-memory cache keyed by slug → org record.
52
+ // `null` = probed and not found (short-circuit on subsequent calls).
53
+ const cache = new Map();
54
+ const notFound = (res) => {
55
+ res.status(404).json({
56
+ code: 'NOT_FOUND',
57
+ message: 'Organization not found',
58
+ });
59
+ };
60
+ const prefixPattern = new RegExp(`^/${escapeRegex(apiPrefix)}/([^/]+)(/.*)?$`);
61
+ return async function tenantRouteRewrite(req, res, next) {
62
+ try {
63
+ const raw = String(req.url ?? '').split('?')[0];
64
+ const query = String(req.url ?? '').slice(raw.length);
65
+ const m = raw.match(prefixPattern);
66
+ if (!m)
67
+ return next();
68
+ const first = m[1];
69
+ const rest = m[2] ?? '';
70
+ // Reserved non-tenant segments (auth, invitations, nested) pass through.
71
+ if (reserved.has(first))
72
+ return next();
73
+ // If the first segment matches a KNOWN model slug, this isn't a tenant
74
+ // route — the consumer is hitting `/api/<modelSlug>` directly (e.g.
75
+ // without a tenant prefix). Skip rewriting.
76
+ if (knownModelSlugs.has(first))
77
+ return next();
78
+ // Look up org. Cache null so repeat lookups are fast.
79
+ if (!cache.has(first)) {
80
+ const delegate = args.prisma[camel(orgModel)] ?? args.prisma[orgModel];
81
+ if (!delegate?.findFirst)
82
+ return next();
83
+ const org = await delegate.findFirst({ where: { [idColumn]: first } }).catch(() => null);
84
+ cache.set(first, org ?? null);
85
+ }
86
+ const org = cache.get(first);
87
+ if (!org) {
88
+ if (strict)
89
+ return notFound(res);
90
+ return next();
91
+ }
92
+ // Membership check (BP-011: cross-tenant → 404, not 403).
93
+ if (enforceMembership && req.user) {
94
+ const userId = req.user.id;
95
+ const urDelegate = args.prisma[camel(userOrgModel)] ?? args.prisma[userOrgModel];
96
+ if (urDelegate?.findFirst) {
97
+ const member = await urDelegate
98
+ .findFirst({ where: { userId, organizationId: org.id } })
99
+ .catch(() => null);
100
+ if (!member)
101
+ return notFound(res);
102
+ }
103
+ }
104
+ // Attach + rewrite.
105
+ req.organization = org;
106
+ req.__orgSlug = first;
107
+ req.url = `/${apiPrefix}${rest}${query}`;
108
+ req.originalUrl = req.url;
109
+ return next();
110
+ }
111
+ catch (err) {
112
+ return next(err);
113
+ }
114
+ };
115
+ }
116
+ function camel(name) {
117
+ if (!name)
118
+ return name;
119
+ return name.charAt(0).toLowerCase() + name.slice(1);
120
+ }
121
+ function escapeRegex(s) {
122
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
123
+ }
124
+ //# sourceMappingURL=tenant-route-rewrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-route-rewrite.js","sourceRoot":"","sources":["../../src/middleware/tenant-route-rewrite.ts"],"names":[],"mappings":"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"}