@stjoseph/prumo 0.0.6 → 0.1.1

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 (140) hide show
  1. package/.prumo-templates/api/auth.md +51 -95
  2. package/.prumo-templates/api/config.md +30 -56
  3. package/.prumo-templates/api/errors.md +64 -44
  4. package/.prumo-templates/api/logging.md +27 -44
  5. package/.prumo-templates/api/observability.md +36 -62
  6. package/.prumo-templates/api/pagination.md +23 -38
  7. package/.prumo-templates/api/routes.md +67 -48
  8. package/.prumo-templates/api/structure.md +130 -0
  9. package/.prumo-templates/client/data.md +4 -3
  10. package/.prumo-templates/core/conventions.md +10 -7
  11. package/.prumo-templates/core/testing.md +43 -26
  12. package/.prumo-templates/core/tooling.md +33 -30
  13. package/.prumo-templates/database/entities.md +53 -61
  14. package/.prumo-templates/database/queries.md +34 -47
  15. package/.prumo-templates/database/transactions.md +28 -38
  16. package/.prumo-templates/mcp/server.md +54 -0
  17. package/.prumo-templates/monorepo/dependencies.md +3 -3
  18. package/.prumo-templates/monorepo/layout.md +1 -1
  19. package/README.md +6 -6
  20. package/dist/asker.js +4 -0
  21. package/dist/cli.js +7 -8
  22. package/dist/commands.js +2 -0
  23. package/dist/compose.js +6 -2
  24. package/dist/context.js +5 -0
  25. package/dist/generate.js +8 -0
  26. package/dist/mcp.js +100 -0
  27. package/dist/questions.js +21 -1
  28. package/dist/version.js +7 -0
  29. package/package.json +4 -3
  30. package/templates/api/.swcrc +13 -0
  31. package/templates/api/biome.jsonc +5 -3
  32. package/templates/api/package.json +21 -27
  33. package/templates/api/pnpm-workspace.yaml +0 -2
  34. package/templates/api/src/domain/shared/errors/domain-error.ts +5 -0
  35. package/templates/api/src/domain/shared/transactions/transaction-manager.ts +5 -0
  36. package/templates/api/src/domain/users/dto/profile-response.dto.ts +14 -0
  37. package/templates/api/src/domain/users/dto/update-profile.dto.ts +10 -0
  38. package/templates/api/src/domain/users/entities/profile.entity.ts +26 -0
  39. package/templates/api/src/domain/users/errors/profile-not-found.error.ts +9 -0
  40. package/templates/api/src/domain/users/repositories/profile.repository.ts +8 -0
  41. package/templates/api/src/domain/users/use-cases/create-profile.use-case.ts +19 -0
  42. package/templates/api/src/domain/users/use-cases/find-profile.use-case.ts +22 -0
  43. package/templates/api/src/domain/users/use-cases/update-profile.use-case.ts +26 -0
  44. package/templates/api/src/infra/auth/auth.cli.ts +5 -0
  45. package/templates/api/src/{auth → infra/auth}/auth.factory.ts +26 -1
  46. package/templates/api/src/infra/config/env.ts +47 -0
  47. package/templates/api/src/infra/database/mikroorm/entities/auth-user.schema.ts +16 -0
  48. package/templates/api/src/infra/database/mikroorm/entities/profile.schema.ts +28 -0
  49. package/templates/api/src/{mikro-orm.config.ts → infra/database/mikroorm/mikro-orm.config.ts} +1 -1
  50. package/templates/api/src/infra/database/mikroorm/mikro-orm.factory.ts +17 -0
  51. package/templates/api/src/infra/database/mikroorm/repositories/mikroorm-profile.repository.ts +17 -0
  52. package/templates/api/src/infra/database/mikroorm/transactions/mikroorm-transaction-manager.ts +14 -0
  53. package/templates/api/src/infra/di/index.ts +32 -0
  54. package/templates/api/src/infra/di/users.di.ts +7 -0
  55. package/templates/api/src/infra/http/app.ts +99 -0
  56. package/templates/api/src/infra/http/controllers/auth.controller.ts +36 -0
  57. package/templates/api/src/infra/http/controllers/health.controller.ts +19 -0
  58. package/templates/api/src/infra/http/controllers/mcp.controller.ts +40 -0
  59. package/templates/api/src/infra/http/controllers/users.controller.ts +35 -0
  60. package/templates/api/src/infra/http/errors/error-handler.ts +116 -0
  61. package/templates/api/src/infra/http/errors/http-error.ts +8 -0
  62. package/templates/api/src/infra/http/fastify.d.ts +17 -0
  63. package/templates/api/src/infra/http/fetch-bridge.ts +42 -0
  64. package/templates/api/src/infra/http/hooks/auth.hook.ts +32 -0
  65. package/templates/api/src/infra/http/hooks/request-context.hook.ts +9 -0
  66. package/templates/api/src/infra/http/server.ts +34 -0
  67. package/templates/api/src/infra/mcp/mcp.server.ts +20 -0
  68. package/templates/api/src/infra/mcp/tools/users.tool.ts +39 -0
  69. package/templates/api/test/domain/users/use-cases/find-profile.use-case.spec.ts +22 -0
  70. package/templates/api/test/domain/users/use-cases/update-profile.use-case.spec.ts +26 -0
  71. package/templates/api/{src → test/infra}/auth/auth.factory.spec.ts +4 -11
  72. package/templates/api/test/infra/database/mikroorm/mikro-orm.factory.spec.ts +8 -0
  73. package/templates/api/test/infra/database/mikroorm/repositories/mikroorm-profile.repository.spec.ts +51 -0
  74. package/templates/api/test/infra/database/mikroorm/transactions/mikroorm-transaction-manager.spec.ts +29 -0
  75. package/templates/api/test/infra/http/app.spec.ts +21 -0
  76. package/templates/api/test/infra/http/controllers/mcp.controller.spec.ts +152 -0
  77. package/templates/api/test/infra/http/controllers/users.controller.spec.ts +105 -0
  78. package/templates/api/test/support/factories/profile.factory.ts +25 -0
  79. package/templates/api/test/{factories → support/factories}/user.factory.ts +1 -1
  80. package/templates/api/test/support/fakes/in-memory-profile.repository.ts +14 -0
  81. package/templates/api/test/{setup.ts → support/setup.ts} +26 -14
  82. package/templates/api/tsconfig.json +3 -6
  83. package/templates/api/vitest.config.mts +4 -4
  84. package/templates/mobile/jest.config.js +1 -1
  85. package/templates/mobile/package.json +2 -2
  86. package/templates/mobile/{src → test}/features/auth/authenticated-layout.spec.tsx +1 -1
  87. package/templates/mobile/{src → test}/features/auth/sign-in-form.spec.tsx +2 -2
  88. package/templates/mobile/{src → test}/features/auth/sign-out-button.spec.tsx +2 -2
  89. package/templates/mobile/{src → test}/features/profile/profile-form.spec.tsx +2 -2
  90. package/templates/mobile/tsconfig.json +2 -1
  91. package/templates/site/{src → test}/app/page.spec.ts +1 -1
  92. package/templates/site/tsconfig.json +2 -1
  93. package/templates/site/vitest.config.mts +4 -2
  94. package/templates/web/package.json +4 -2
  95. package/templates/web/src/features/auth/auth-client.ts +5 -0
  96. package/templates/web/src/features/auth/sign-in-form.tsx +10 -3
  97. package/templates/web/src/features/oauth/consent-form.tsx +63 -0
  98. package/templates/web/src/routeTree.gen.ts +21 -2
  99. package/templates/web/src/routes/_authenticated/consent.tsx +21 -0
  100. package/templates/web/{src → test}/app.spec.tsx +2 -2
  101. package/templates/web/{src → test}/features/auth/sign-in-form.spec.tsx +2 -2
  102. package/templates/web/test/features/oauth/consent-form.spec.tsx +45 -0
  103. package/templates/web/{src → test}/features/profile/profile-form.spec.tsx +2 -2
  104. package/templates/web/test/{render-app.tsx → support/render-app.tsx} +1 -1
  105. package/templates/web/tsconfig.json +2 -1
  106. package/templates/web/vite.config.ts +3 -3
  107. package/.prumo-templates/api/modules.md +0 -117
  108. package/templates/api/nest-cli.json +0 -10
  109. package/templates/api/src/app.module.ts +0 -46
  110. package/templates/api/src/auth/auth.constants.ts +0 -2
  111. package/templates/api/src/auth/auth.controller.ts +0 -19
  112. package/templates/api/src/auth/auth.guard.ts +0 -43
  113. package/templates/api/src/auth/auth.module.ts +0 -30
  114. package/templates/api/src/auth/auth.ts +0 -4
  115. package/templates/api/src/auth/current-user.decorator.ts +0 -9
  116. package/templates/api/src/auth/express.d.ts +0 -10
  117. package/templates/api/src/auth/public.decorator.ts +0 -4
  118. package/templates/api/src/config/env.module.ts +0 -11
  119. package/templates/api/src/config/env.ts +0 -81
  120. package/templates/api/src/errors/problem-details.filter.ts +0 -86
  121. package/templates/api/src/errors/validation-exception.factory.ts +0 -30
  122. package/templates/api/src/health/health.controller.ts +0 -32
  123. package/templates/api/src/health/health.module.ts +0 -9
  124. package/templates/api/src/main.ts +0 -43
  125. package/templates/api/src/mikro-orm.factory.ts +0 -14
  126. package/templates/api/src/request-context/request-context.module.ts +0 -9
  127. package/templates/api/src/request-context/request-context.service.ts +0 -17
  128. package/templates/api/src/request-context/request-id.middleware.ts +0 -19
  129. package/templates/api/src/users/dto/update-profile.dto.ts +0 -20
  130. package/templates/api/src/users/entities/profile.entity.ts +0 -27
  131. package/templates/api/src/users/users.controller.ts +0 -23
  132. package/templates/api/src/users/users.module.ts +0 -10
  133. package/templates/api/src/users/users.service.spec.ts +0 -44
  134. package/templates/api/src/users/users.service.ts +0 -36
  135. package/templates/api/test/factories/profile.factory.ts +0 -26
  136. package/templates/api/tsconfig.build.json +0 -8
  137. /package/templates/api/test/{global-setup.ts → support/global-setup.ts} +0 -0
  138. /package/templates/mobile/test/{fake-transport.ts → support/fake-transport.ts} +0 -0
  139. /package/templates/web/test/{fake-transport.ts → support/fake-transport.ts} +0 -0
  140. /package/templates/web/test/{setup.ts → support/setup.ts} +0 -0
@@ -1,43 +0,0 @@
1
- import { MikroORM } from '@mikro-orm/postgresql'
2
- import { ClassSerializerInterceptor, ValidationPipe, VersioningType } from '@nestjs/common'
3
- import { NestFactory, Reflector } from '@nestjs/core'
4
- import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
5
- import { Logger } from 'nestjs-pino'
6
- import { AppModule } from './app.module'
7
- import { Env } from './config/env'
8
- import { validationExceptionFactory } from './errors/validation-exception.factory'
9
-
10
- async function bootstrap(): Promise<void> {
11
- const app = await NestFactory.create(AppModule, { bufferLogs: true })
12
- const env = app.get(Env)
13
-
14
- app.useLogger(app.get(Logger))
15
- app.setGlobalPrefix('api')
16
- app.enableCors({ origin: env.WEB_ORIGIN, credentials: true, exposedHeaders: ['X-Request-Id'] })
17
- app.enableVersioning({ type: VersioningType.URI })
18
-
19
- app.useGlobalPipes(
20
- new ValidationPipe({
21
- whitelist: true,
22
- forbidNonWhitelisted: true,
23
- transform: true,
24
- exceptionFactory: validationExceptionFactory,
25
- }),
26
- )
27
- app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)))
28
-
29
- if (env.NODE_ENV !== 'production') {
30
- const document = SwaggerModule.createDocument(
31
- app,
32
- new DocumentBuilder().setTitle('API').setVersion('1').build(),
33
- )
34
- SwaggerModule.setup('api/docs', app, document)
35
- }
36
-
37
- // MikroORM 7 no longer connects on init, so readiness would report the database down until a first query.
38
- await app.get(MikroORM).connect()
39
-
40
- await app.listen(env.PORT)
41
- }
42
-
43
- void bootstrap()
@@ -1,14 +0,0 @@
1
- import { Migrator } from '@mikro-orm/migrations'
2
- import { defineConfig } from '@mikro-orm/postgresql'
3
- import type { Env } from './config/env'
4
- import { Profile } from './users/entities/profile.entity'
5
-
6
- export function createOrmConfig(env: Pick<Env, 'DATABASE_URL' | 'NODE_ENV'>) {
7
- return defineConfig({
8
- clientUrl: env.DATABASE_URL,
9
- entities: [Profile],
10
- extensions: [Migrator],
11
- migrations: { path: './migrations', snapshot: false },
12
- debug: env.NODE_ENV === 'development',
13
- })
14
- }
@@ -1,9 +0,0 @@
1
- import { Global, Module } from '@nestjs/common'
2
- import { RequestContextService } from './request-context.service'
3
-
4
- @Global()
5
- @Module({
6
- providers: [RequestContextService],
7
- exports: [RequestContextService],
8
- })
9
- export class RequestContextModule {}
@@ -1,17 +0,0 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks'
2
- import { Injectable } from '@nestjs/common'
3
-
4
- type Store = { requestId: string }
5
-
6
- @Injectable()
7
- export class RequestContextService {
8
- private readonly storage = new AsyncLocalStorage<Store>()
9
-
10
- run<T>(requestId: string, callback: () => T): T {
11
- return this.storage.run({ requestId }, callback)
12
- }
13
-
14
- get requestId(): string | undefined {
15
- return this.storage.getStore()?.requestId
16
- }
17
- }
@@ -1,19 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { Injectable, type NestMiddleware } from '@nestjs/common'
3
- import type { NextFunction, Request, Response } from 'express'
4
- import { RequestContextService } from './request-context.service'
5
-
6
- export const REQUEST_ID_HEADER = 'x-request-id'
7
-
8
- @Injectable()
9
- export class RequestIdMiddleware implements NestMiddleware {
10
- constructor(private readonly context: RequestContextService) {}
11
-
12
- use(request: Request, response: Response, next: NextFunction): void {
13
- const inbound = request.header(REQUEST_ID_HEADER)
14
- const requestId = inbound && inbound.length > 0 ? inbound : randomUUID()
15
-
16
- response.setHeader(REQUEST_ID_HEADER, requestId)
17
- this.context.run(requestId, next)
18
- }
19
- }
@@ -1,20 +0,0 @@
1
- import { IsIn, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'
2
-
3
- const LOCALES = ['en', 'pt-BR'] as const
4
-
5
- export class UpdateProfileDto {
6
- @IsOptional()
7
- @IsString()
8
- @MinLength(1)
9
- @MaxLength(80)
10
- displayName?: string
11
-
12
- @IsOptional()
13
- @IsIn(LOCALES)
14
- locale?: (typeof LOCALES)[number]
15
-
16
- @IsOptional()
17
- @IsString()
18
- @MaxLength(64)
19
- timezone?: string
20
- }
@@ -1,27 +0,0 @@
1
- import { defineEntity, p } from '@mikro-orm/core'
2
-
3
- const ProfileSchema = defineEntity({
4
- name: 'Profile',
5
- properties: {
6
- id: p.uuid().primary().defaultRaw('uuidv7()'),
7
- userId: p.uuid().unique(),
8
- displayName: p.text(),
9
- locale: p.text().default('en'),
10
- timezone: p.text().default('UTC'),
11
- createdAt: p
12
- .datetime()
13
- .onCreate(() => new Date())
14
- .defaultRaw('now()'),
15
- updatedAt: p
16
- .datetime()
17
- .onCreate(() => new Date())
18
- .onUpdate(() => new Date())
19
- .defaultRaw('now()'),
20
- },
21
- })
22
-
23
- export class Profile extends ProfileSchema.class {}
24
-
25
- ProfileSchema.setClass(Profile)
26
-
27
- export { ProfileSchema }
@@ -1,23 +0,0 @@
1
- import { Body, Controller, Get, Patch } from '@nestjs/common'
2
- import { ApiTags } from '@nestjs/swagger'
3
- import type { AuthUser } from '@/auth/auth.factory'
4
- import { CurrentUser } from '@/auth/current-user.decorator'
5
- import { UpdateProfileDto } from './dto/update-profile.dto'
6
- import type { Profile } from './entities/profile.entity'
7
- import { UsersService } from './users.service'
8
-
9
- @ApiTags('users')
10
- @Controller({ path: 'users', version: '1' })
11
- export class UsersController {
12
- constructor(private readonly users: UsersService) {}
13
-
14
- @Get('me')
15
- findMe(@CurrentUser() user: AuthUser): Promise<Profile> {
16
- return this.users.findProfile(user.id)
17
- }
18
-
19
- @Patch('me')
20
- updateMe(@CurrentUser() user: AuthUser, @Body() changes: UpdateProfileDto): Promise<Profile> {
21
- return this.users.updateProfile(user.id, changes)
22
- }
23
- }
@@ -1,10 +0,0 @@
1
- import { Module } from '@nestjs/common'
2
- import { UsersController } from './users.controller'
3
- import { UsersService } from './users.service'
4
-
5
- @Module({
6
- controllers: [UsersController],
7
- providers: [UsersService],
8
- exports: [UsersService],
9
- })
10
- export class UsersModule {}
@@ -1,44 +0,0 @@
1
- import { NotFoundException } from '@nestjs/common'
2
- import { plainToInstance } from 'class-transformer'
3
- import { beforeEach, describe, expect, it } from 'vitest'
4
- import { createProfile } from '../../test/factories/profile.factory'
5
- import { testOrm } from '../../test/setup'
6
- import { UpdateProfileDto } from './dto/update-profile.dto'
7
- import { UsersService } from './users.service'
8
-
9
- describe('UsersService', () => {
10
- let service: UsersService
11
-
12
- beforeEach(() => {
13
- service = new UsersService(testOrm().em.fork())
14
- })
15
-
16
- it('finds the profile belonging to a user', async () => {
17
- const em = testOrm().em.fork()
18
- const created = await createProfile(em, { displayName: 'Ana' })
19
-
20
- await expect(service.findProfile(created.userId)).resolves.toMatchObject({
21
- displayName: 'Ana',
22
- })
23
- })
24
-
25
- it('refuses a user without a profile', async () => {
26
- await expect(service.findProfile(crypto.randomUUID())).rejects.toBeInstanceOf(NotFoundException)
27
- })
28
-
29
- it('applies only the fields the request carries', async () => {
30
- const em = testOrm().em.fork()
31
- const created = await createProfile(em, {
32
- displayName: 'Ana',
33
- timezone: 'UTC',
34
- })
35
-
36
- const changes = plainToInstance(UpdateProfileDto, { timezone: 'America/Sao_Paulo' })
37
- const updated = await service.updateProfile(created.userId, changes)
38
-
39
- expect(updated).toMatchObject({
40
- displayName: 'Ana',
41
- timezone: 'America/Sao_Paulo',
42
- })
43
- })
44
- })
@@ -1,36 +0,0 @@
1
- import { EntityManager } from '@mikro-orm/postgresql'
2
- import { Injectable, NotFoundException } from '@nestjs/common'
3
- import type { UpdateProfileDto } from './dto/update-profile.dto'
4
- import { Profile } from './entities/profile.entity'
5
-
6
- @Injectable()
7
- export class UsersService {
8
- constructor(private readonly em: EntityManager) {}
9
-
10
- async findProfile(userId: string): Promise<Profile> {
11
- const profile = await this.em.findOne(Profile, { userId })
12
-
13
- if (profile === null) {
14
- throw new NotFoundException('Profile not found')
15
- }
16
-
17
- return profile
18
- }
19
-
20
- async updateProfile(userId: string, changes: UpdateProfileDto): Promise<Profile> {
21
- const profile = await this.findProfile(userId)
22
-
23
- this.em.assign(profile, changes, { ignoreUndefined: true })
24
- await this.em.flush()
25
-
26
- return profile
27
- }
28
-
29
- async createProfile(userId: string, displayName: string): Promise<Profile> {
30
- const profile = this.em.create(Profile, { userId, displayName })
31
-
32
- await this.em.flush()
33
-
34
- return profile
35
- }
36
- }
@@ -1,26 +0,0 @@
1
- import type { EntityManager } from '@mikro-orm/postgresql'
2
- import { Profile } from '@/users/entities/profile.entity'
3
- import { createUser } from './user.factory'
4
-
5
- type ProfileOverrides = Partial<{
6
- userId: string
7
- displayName: string
8
- locale: string
9
- timezone: string
10
- }>
11
-
12
- export async function createProfile(
13
- em: EntityManager,
14
- overrides: ProfileOverrides = {},
15
- ): Promise<Profile> {
16
- const profile = em.create(Profile, {
17
- userId: overrides.userId ?? (await createUser(em)),
18
- displayName: overrides.displayName ?? 'Test Person',
19
- locale: overrides.locale ?? 'en',
20
- timezone: overrides.timezone ?? 'UTC',
21
- })
22
-
23
- await em.flush()
24
-
25
- return profile
26
- }
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src"
5
- },
6
- "include": ["src"],
7
- "exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
8
- }