{"_id":"@axonsdev/hateoas-nestjs","name":"@axonsdev/hateoas-nestjs","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@axonsdev/hateoas-nestjs","version":"0.1.0","license":"MPL-2.0","author":{"email":"nprin@axons.fr"},"type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"publishConfig":{"access":"public"},"peerDependencies":{"@nestjs/common":">=10.0.0"},"dependencies":{"@axonsdev/hateoas-siren":"^0.1.0","@axonsdev/hateoas-core":"^0.1.0"},"scripts":{"build":"tsc -b tsconfig.json"},"_id":"@axonsdev/hateoas-nestjs@0.1.0","description":"NestJS helpers for composing Siren hypermedia responses.","_integrity":"sha512-jU5+83+QKwHBjfksjGHzNmXHLQ5YgdmNDIzomtfaCA69i86CTEx+cEFKDHbh+z5jvtH8pmDrCojhjiM3r4XugA==","_resolved":"/tmp/c2070d3190e670ac6e5d30441a01b232/axonsdev-hateoas-nestjs-0.1.0.tgz","_from":"file:axonsdev-hateoas-nestjs-0.1.0.tgz","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-jU5+83+QKwHBjfksjGHzNmXHLQ5YgdmNDIzomtfaCA69i86CTEx+cEFKDHbh+z5jvtH8pmDrCojhjiM3r4XugA==","shasum":"fc341a4214d2fd6eb787d411f2e7f8a24a548ae8","tarball":"https://registry.npmjs.org/@axonsdev/hateoas-nestjs/-/hateoas-nestjs-0.1.0.tgz","fileCount":25,"unpackedSize":42168,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCID+6TJLmlWFoiWA7+NE28NVI6m3qoFl+X1LY9XwJ698OAiEAmJcEHNzVYjQbXXeuykgsH42dxG13K8vmPrQHWxAIMbc="}]},"_npmUser":{"name":"axonsdev","email":"nprin@axons.fr"},"directories":{},"maintainers":[{"name":"axonsdev","email":"nprin@axons.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hateoas-nestjs_0.1.0_1777393228109_0.11902266928693317"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-28T16:20:27.983Z","0.1.0":"2026-04-28T16:20:28.314Z","modified":"2026-04-28T16:20:28.513Z"},"maintainers":[{"name":"axonsdev","email":"nprin@axons.fr"}],"description":"NestJS helpers for composing Siren hypermedia responses.","author":{"email":"nprin@axons.fr"},"license":"MPL-2.0","readme":"# @axonsdev/hateoas-nestjs\n\nNestJS helpers for composing Siren hypermedia responses.\n\nThis package lets backend code describe resource properties, links, profiles, and actions, then compose Siren responses from domain entities.\n\n## Responsibilities\n\n- Register resource definitions and route factories.\n- Compose single-resource and collection responses.\n- Resolve Nest providers while building hypermedia actions.\n- Provide Siren builders for custom responses.\n- Mark HTTP responses with the Siren content type.\n- Provide small testing helpers for Siren assertions.\n\n## Main Exports\n\n```ts\nHateoasModule\nHateoasService\ndefineSirenResource\nHypermediaResourceDefinition\nResourceRegistry\nRouteUrlResolver\nsirenEntity\nsirenAction\nsirenLink\nsirenField\nSIREN_CONTENT_TYPE\nSirenResponse\nexpectSiren\n```\n\n## Registering The Module\n\n```ts\n@Module({\n  imports: [\n    HateoasModule.forRoot({\n      resources: [caseResource],\n      routes: {\n        'cases.findAll': () => '/api/cases',\n        'cases.findOne': ({ id }) => `/api/cases/${id}`,\n        'cases.approve': ({ id }) => `/api/cases/${id}/approve`,\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n`HateoasModule` is global so feature modules can inject `HateoasService` after the root module registers resources and routes.\n\n## Defining A Resource\n\n```ts\nexport const caseResource = defineSirenResource<CaseEntity, { user: DemoUser }>(CaseEntity, {\n  name: 'case',\n  classes: ['case'],\n  id: (entity) => entity.id,\n\n  properties: {\n    id: 'id',\n    title: 'title',\n    status: 'status',\n  },\n\n  profiles: {\n    list: {\n      expose: ['id', 'title', 'status'],\n      links: ['self'],\n      actions: [],\n    },\n    detail: {\n      expose: ['id', 'title', 'status'],\n      links: ['self', 'collection'],\n      actions: ['approve'],\n    },\n  },\n\n  links: {\n    self: ({ entity, url }) => sirenLink('self', url.route('cases.findOne', { id: entity.id })),\n    collection: ({ url }) => sirenLink('collection', url.route('cases.findAll')),\n  },\n\n  actions: {\n    approve: ({ entity, context, services, url }) => {\n      const policy = services.get<CaseTransitionPolicy>(CaseTransitionPolicy);\n\n      if (!policy.getAvailableActions(entity, context.user).includes('approve')) {\n        return null;\n      }\n\n      return sirenAction('approve')\n        .title('Approve')\n        .method('POST')\n        .href(url.route('cases.approve', { id: entity.id }))\n        .type('application/json')\n        .build();\n    },\n  },\n});\n```\n\n## Profiles\n\nProfiles control which properties, links, and actions are exposed for a given use case.\n\nCommon patterns:\n\n```txt\nlist   -> compact embedded collection items\ndetail -> full resource with actions\n```\n\nProfiles let one resource definition serve multiple response shapes without duplicating business mapping.\n\n## Composing Responses\n\nSingle resource:\n\n```ts\nreturn this.hateoas\n  .resource<CaseEntity, { user: DemoUser }>(caseEntity)\n  .profile('detail')\n  .withContext({ user })\n  .toResponse();\n```\n\nCollection:\n\n```ts\nreturn this.hateoas\n  .collection<CaseEntity, { user: DemoUser }>(CaseEntity, cases)\n  .profile('list')\n  .withContext({ user })\n  .toResponse();\n```\n\nThe context is passed to property selectors, link resolvers, and action resolvers.\n\n## Service Resolution\n\nAction resolvers receive a `services` object:\n\n```ts\nconst policy = services.get<CaseTransitionPolicy>(CaseTransitionPolicy);\n```\n\nThis uses Nest's `ModuleRef` with `{ strict: false }`, which allows resource builders to ask for application providers without coupling the package to a specific feature module.\n\n## Custom Siren Builders\n\nUse builders when a response is more complex than a generic resource definition.\n\n```ts\nreturn sirenEntity()\n  .addClass('api-root')\n  .properties({ name: 'API' })\n  .link(sirenLink('self', '/api'))\n  .build();\n```\n\nAvailable helpers:\n\n```ts\nsirenEntity()\nsirenAction(name)\nsirenLink(rel, href, extra)\nsirenField(field)\n```\n\n## Response Decorator\n\n```ts\n@Get(':id')\n@SirenResponse()\nfindOne() {\n  return response;\n}\n```\n\n`@SirenResponse()` sets the response content type to Siren.\n\n## Testing\n\nThe package exports Siren testing helpers from:\n\n```txt\nsrc/testing/siren-expect.ts\n```\n\nUse them to assert generated Siren structures without repeating low-level object checks.\n\n## Notes\n\n- Resource definitions describe representation, not persistence.\n- Domain rules should live in application policies or services.\n- Returning `null`, `false`, or `undefined` from an action resolver omits that action.\n- Route names decouple resource builders from hardcoded route strings.\n","readmeFilename":"README.md","_rev":"1-6f712e6e5b22bf8a1ec00824e9b95575"}