@trinacria/cli 0.1.1-alpha.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.

Potentially problematic release.


This version of @trinacria/cli might be problematic. Click here for more details.

Files changed (129) hide show
  1. package/README.md +109 -0
  2. package/README.npm.md +109 -0
  3. package/dist/cli.d.ts +19 -0
  4. package/dist/cli.js +88 -0
  5. package/dist/commands/build.d.ts +2 -0
  6. package/dist/commands/build.js +102 -0
  7. package/dist/commands/dev.d.ts +28 -0
  8. package/dist/commands/dev.js +186 -0
  9. package/dist/commands/new.d.ts +41 -0
  10. package/dist/commands/new.js +280 -0
  11. package/dist/commands/start.d.ts +13 -0
  12. package/dist/commands/start.js +110 -0
  13. package/dist/config/config.contract.d.ts +9 -0
  14. package/dist/config/config.contract.js +2 -0
  15. package/dist/config/default-config.d.ts +2 -0
  16. package/dist/config/default-config.js +11 -0
  17. package/dist/config/load-config.d.ts +2 -0
  18. package/dist/config/load-config.js +113 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +5 -0
  21. package/dist/templates/api-events-rabbitmq/.env.example +23 -0
  22. package/dist/templates/api-events-rabbitmq/Dockerfile +18 -0
  23. package/dist/templates/api-events-rabbitmq/README.md +97 -0
  24. package/dist/templates/api-events-rabbitmq/docker-compose.yml +30 -0
  25. package/dist/templates/api-events-rabbitmq/package.json +21 -0
  26. package/dist/templates/api-events-rabbitmq/src/global/config.service.ts +95 -0
  27. package/dist/templates/api-events-rabbitmq/src/global/controllers/swagger/docs.html +28 -0
  28. package/dist/templates/api-events-rabbitmq/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
  29. package/dist/templates/api-events-rabbitmq/src/global/rabbitmq.service.ts +86 -0
  30. package/dist/templates/api-events-rabbitmq/src/global/register-global-controllers.ts +20 -0
  31. package/dist/templates/api-events-rabbitmq/src/main.ts +89 -0
  32. package/dist/templates/api-events-rabbitmq/src/modules/events/dto/event.dto.ts +24 -0
  33. package/dist/templates/api-events-rabbitmq/src/modules/events/dto/index.ts +2 -0
  34. package/dist/templates/api-events-rabbitmq/src/modules/events/dto/publish-event.dto.ts +12 -0
  35. package/dist/templates/api-events-rabbitmq/src/modules/events/events.controller.ts +76 -0
  36. package/dist/templates/api-events-rabbitmq/src/modules/events/events.module.ts +27 -0
  37. package/dist/templates/api-events-rabbitmq/src/modules/events/events.provider.ts +28 -0
  38. package/dist/templates/api-events-rabbitmq/src/modules/events/events.service.ts +19 -0
  39. package/dist/templates/api-events-rabbitmq/src/modules/events/events.store.ts +42 -0
  40. package/dist/templates/api-events-rabbitmq/src/modules/events/events.tokens.ts +11 -0
  41. package/dist/templates/api-events-rabbitmq/trinacria.config.mjs +12 -0
  42. package/dist/templates/api-events-rabbitmq/tsconfig.json +14 -0
  43. package/dist/templates/api-events-redis/.env.example +18 -0
  44. package/dist/templates/api-events-redis/Dockerfile +18 -0
  45. package/dist/templates/api-events-redis/README.md +97 -0
  46. package/dist/templates/api-events-redis/docker-compose.yml +33 -0
  47. package/dist/templates/api-events-redis/package.json +18 -0
  48. package/dist/templates/api-events-redis/src/global/config.service.ts +93 -0
  49. package/dist/templates/api-events-redis/src/global/controllers/swagger/docs.html +28 -0
  50. package/dist/templates/api-events-redis/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
  51. package/dist/templates/api-events-redis/src/global/redis.service.ts +50 -0
  52. package/dist/templates/api-events-redis/src/global/register-global-controllers.ts +20 -0
  53. package/dist/templates/api-events-redis/src/main.ts +88 -0
  54. package/dist/templates/api-events-redis/src/modules/events/dto/event.dto.ts +24 -0
  55. package/dist/templates/api-events-redis/src/modules/events/dto/index.ts +2 -0
  56. package/dist/templates/api-events-redis/src/modules/events/dto/publish-event.dto.ts +12 -0
  57. package/dist/templates/api-events-redis/src/modules/events/events.controller.ts +76 -0
  58. package/dist/templates/api-events-redis/src/modules/events/events.module.ts +27 -0
  59. package/dist/templates/api-events-redis/src/modules/events/events.provider.ts +28 -0
  60. package/dist/templates/api-events-redis/src/modules/events/events.service.ts +19 -0
  61. package/dist/templates/api-events-redis/src/modules/events/events.store.ts +42 -0
  62. package/dist/templates/api-events-redis/src/modules/events/events.tokens.ts +11 -0
  63. package/dist/templates/api-events-redis/trinacria.config.mjs +12 -0
  64. package/dist/templates/api-events-redis/tsconfig.json +14 -0
  65. package/dist/templates/api-mongoose-mongodb/.env.example +17 -0
  66. package/dist/templates/api-mongoose-mongodb/Dockerfile +18 -0
  67. package/dist/templates/api-mongoose-mongodb/README.md +98 -0
  68. package/dist/templates/api-mongoose-mongodb/docker-compose.yml +34 -0
  69. package/dist/templates/api-mongoose-mongodb/package.json +17 -0
  70. package/dist/templates/api-mongoose-mongodb/src/global/config.service.ts +92 -0
  71. package/dist/templates/api-mongoose-mongodb/src/global/controllers/swagger/docs.html +28 -0
  72. package/dist/templates/api-mongoose-mongodb/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
  73. package/dist/templates/api-mongoose-mongodb/src/global/mongoose-schema.provider.ts +18 -0
  74. package/dist/templates/api-mongoose-mongodb/src/global/mongoose.service.ts +36 -0
  75. package/dist/templates/api-mongoose-mongodb/src/global/register-global-controllers.ts +20 -0
  76. package/dist/templates/api-mongoose-mongodb/src/main.ts +70 -0
  77. package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/create-user.dto.ts +14 -0
  78. package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/index.ts +2 -0
  79. package/dist/templates/api-mongoose-mongodb/src/modules/users/dto/public-user.dto.ts +22 -0
  80. package/dist/templates/api-mongoose-mongodb/src/modules/users/users.controller.ts +89 -0
  81. package/dist/templates/api-mongoose-mongodb/src/modules/users/users.module.ts +17 -0
  82. package/dist/templates/api-mongoose-mongodb/src/modules/users/users.schema.ts +35 -0
  83. package/dist/templates/api-mongoose-mongodb/src/modules/users/users.service.ts +35 -0
  84. package/dist/templates/api-mongoose-mongodb/src/modules/users/users.tokens.ts +9 -0
  85. package/dist/templates/api-mongoose-mongodb/trinacria.config.mjs +12 -0
  86. package/dist/templates/api-mongoose-mongodb/tsconfig.json +14 -0
  87. package/dist/templates/api-prisma-postgresql/.env.example +19 -0
  88. package/dist/templates/api-prisma-postgresql/Dockerfile +24 -0
  89. package/dist/templates/api-prisma-postgresql/README.md +107 -0
  90. package/dist/templates/api-prisma-postgresql/docker-compose.yml +38 -0
  91. package/dist/templates/api-prisma-postgresql/package.json +26 -0
  92. package/dist/templates/api-prisma-postgresql/prisma/schema.prisma +15 -0
  93. package/dist/templates/api-prisma-postgresql/prisma.config.ts +9 -0
  94. package/dist/templates/api-prisma-postgresql/src/global/config.service.ts +92 -0
  95. package/dist/templates/api-prisma-postgresql/src/global/controllers/swagger/docs.html +28 -0
  96. package/dist/templates/api-prisma-postgresql/src/global/controllers/swagger/swagger-docs.controller.ts +79 -0
  97. package/dist/templates/api-prisma-postgresql/src/global/prisma.service.ts +20 -0
  98. package/dist/templates/api-prisma-postgresql/src/global/register-global-controllers.ts +20 -0
  99. package/dist/templates/api-prisma-postgresql/src/main.ts +70 -0
  100. package/dist/templates/api-prisma-postgresql/src/modules/users/dto/create-user.dto.ts +14 -0
  101. package/dist/templates/api-prisma-postgresql/src/modules/users/dto/index.ts +2 -0
  102. package/dist/templates/api-prisma-postgresql/src/modules/users/dto/public-user.dto.ts +22 -0
  103. package/dist/templates/api-prisma-postgresql/src/modules/users/users.controller.ts +89 -0
  104. package/dist/templates/api-prisma-postgresql/src/modules/users/users.module.ts +15 -0
  105. package/dist/templates/api-prisma-postgresql/src/modules/users/users.service.ts +36 -0
  106. package/dist/templates/api-prisma-postgresql/src/modules/users/users.tokens.ts +7 -0
  107. package/dist/templates/api-prisma-postgresql/trinacria.config.mjs +12 -0
  108. package/dist/templates/api-prisma-postgresql/tsconfig.json +14 -0
  109. package/dist/templates/app-starter/.env.example +1 -0
  110. package/dist/templates/app-starter/Dockerfile +13 -0
  111. package/dist/templates/app-starter/README.md +32 -0
  112. package/dist/templates/app-starter/docker-compose.yml +8 -0
  113. package/dist/templates/app-starter/package.json +14 -0
  114. package/dist/templates/app-starter/src/main.ts +14 -0
  115. package/dist/templates/app-starter/trinacria.config.mjs +12 -0
  116. package/dist/templates/app-starter/tsconfig.json +14 -0
  117. package/dist/templates/cron-example/.env.example +11 -0
  118. package/dist/templates/cron-example/Dockerfile +13 -0
  119. package/dist/templates/cron-example/README.md +68 -0
  120. package/dist/templates/cron-example/docker-compose.yml +11 -0
  121. package/dist/templates/cron-example/package.json +15 -0
  122. package/dist/templates/cron-example/src/config.service.ts +46 -0
  123. package/dist/templates/cron-example/src/main.ts +41 -0
  124. package/dist/templates/cron-example/src/modules/cron/cron.module.ts +15 -0
  125. package/dist/templates/cron-example/src/modules/cron/cron.tokens.ts +6 -0
  126. package/dist/templates/cron-example/src/modules/cron/example-cron-jobs.provider.ts +48 -0
  127. package/dist/templates/cron-example/trinacria.config.mjs +12 -0
  128. package/dist/templates/cron-example/tsconfig.json +14 -0
  129. package/package.json +32 -0
@@ -0,0 +1,24 @@
1
+ import { s } from "@trinacria/schema";
2
+
3
+ export interface EventDto {
4
+ id: string;
5
+ name: string;
6
+ payload: {
7
+ message: string;
8
+ };
9
+ receivedAt: Date;
10
+ }
11
+
12
+ export const EventDtoSchema = s.objectOf<EventDto>()(
13
+ {
14
+ id: s.string({ trim: true, minLength: 1 }),
15
+ name: s.string({ trim: true, minLength: 1 }),
16
+ payload: s.object({
17
+ message: s.string({ trim: true, minLength: 1 }),
18
+ }),
19
+ receivedAt: s.date(),
20
+ },
21
+ { strict: true },
22
+ );
23
+
24
+ export const EventListDtoSchema = s.array(EventDtoSchema);
@@ -0,0 +1,2 @@
1
+ export * from "./event.dto";
2
+ export * from "./publish-event.dto";
@@ -0,0 +1,12 @@
1
+ import { s } from "@trinacria/schema";
2
+
3
+ export interface PublishEventDto {
4
+ message: string;
5
+ }
6
+
7
+ export const PublishEventDtoSchema = s.objectOf<PublishEventDto>()(
8
+ {
9
+ message: s.string({ trim: true, minLength: 1 }),
10
+ },
11
+ { strict: true },
12
+ );
@@ -0,0 +1,76 @@
1
+ import { HttpController, HttpContext, response } from "@trinacria/http";
2
+ import type { EventBus } from "@trinacria/events";
3
+ import { EventListDtoSchema, PublishEventDtoSchema } from "./dto";
4
+ import { DEMO_EVENT_NAME } from "./events.provider";
5
+ import { EventsService } from "./events.service";
6
+
7
+ export class EventsController extends HttpController {
8
+ constructor(
9
+ private readonly eventsService: EventsService,
10
+ private readonly eventBus: EventBus,
11
+ ) {
12
+ super();
13
+ }
14
+
15
+ routes() {
16
+ return this.router()
17
+ .get("/health", this.healthCheck, {
18
+ docs: {
19
+ tags: ["Health"],
20
+ summary: "Health check",
21
+ },
22
+ })
23
+ .get("/events", this.listEvents, {
24
+ docs: {
25
+ tags: ["Events"],
26
+ summary: "List consumed events",
27
+ responses: {
28
+ 200: {
29
+ description: "Consumed events",
30
+ schema: EventListDtoSchema.toOpenApi(),
31
+ },
32
+ },
33
+ },
34
+ })
35
+ .post("/events/publish", this.publishEvent, {
36
+ docs: {
37
+ tags: ["Events"],
38
+ summary: "Publish demo event to Redis transport",
39
+ requestBody: {
40
+ required: true,
41
+ schema: PublishEventDtoSchema.toOpenApi(),
42
+ },
43
+ responses: {
44
+ 202: {
45
+ description: "Event accepted",
46
+ },
47
+ },
48
+ },
49
+ })
50
+ .build();
51
+ }
52
+
53
+ healthCheck() {
54
+ return { status: "ok" };
55
+ }
56
+
57
+ listEvents() {
58
+ return this.eventsService.list();
59
+ }
60
+
61
+ async publishEvent(ctx: HttpContext) {
62
+ const payload = PublishEventDtoSchema.parse(ctx.body);
63
+
64
+ await this.eventBus.emit(DEMO_EVENT_NAME, {
65
+ message: payload.message,
66
+ });
67
+
68
+ return response(
69
+ {
70
+ status: "accepted",
71
+ event: DEMO_EVENT_NAME,
72
+ },
73
+ { status: 202 },
74
+ );
75
+ }
76
+ }
@@ -0,0 +1,27 @@
1
+ import { classProvider, defineModule } from "@trinacria/core";
2
+ import { EVENT_BUS_TOKEN, eventProvider } from "@trinacria/events";
3
+ import { httpProvider } from "@trinacria/http";
4
+ import {
5
+ EVENTS_CONTROLLER,
6
+ EVENTS_PROVIDER,
7
+ EVENTS_SERVICE,
8
+ EVENTS_STORE,
9
+ } from "./events.tokens";
10
+ import { EventsController } from "./events.controller";
11
+ import { EventsProvider } from "./events.provider";
12
+ import { EventsService } from "./events.service";
13
+ import { EventsStore } from "./events.store";
14
+
15
+ export const EventsModule = defineModule({
16
+ name: "EventsModule",
17
+ providers: [
18
+ classProvider(EVENTS_STORE, EventsStore),
19
+ classProvider(EVENTS_SERVICE, EventsService, [EVENTS_STORE]),
20
+ httpProvider(EVENTS_CONTROLLER, EventsController, [
21
+ EVENTS_SERVICE,
22
+ EVENT_BUS_TOKEN,
23
+ ]),
24
+ eventProvider(EVENTS_PROVIDER, EventsProvider, [EVENTS_SERVICE]),
25
+ ],
26
+ exports: [EVENTS_SERVICE, EVENTS_CONTROLLER, EVENTS_PROVIDER],
27
+ });
@@ -0,0 +1,28 @@
1
+ import type { EventProvider } from "@trinacria/events";
2
+ import { EventsService } from "./events.service";
3
+
4
+ export const DEMO_EVENT_NAME = "events.demo";
5
+
6
+ export class EventsProvider implements EventProvider {
7
+ constructor(private readonly eventsService: EventsService) {}
8
+
9
+ subscriptions() {
10
+ return [
11
+ {
12
+ event: DEMO_EVENT_NAME,
13
+ handler: async (payload: unknown) => {
14
+ if (
15
+ payload &&
16
+ typeof payload === "object" &&
17
+ "message" in payload &&
18
+ typeof payload.message === "string"
19
+ ) {
20
+ this.eventsService.add(DEMO_EVENT_NAME, {
21
+ message: payload.message,
22
+ });
23
+ }
24
+ },
25
+ },
26
+ ];
27
+ }
28
+ }
@@ -0,0 +1,19 @@
1
+ import type { EventRecord } from "./events.store";
2
+ import { EventsStore } from "./events.store";
3
+
4
+ export class EventsService {
5
+ constructor(private readonly store: EventsStore) {}
6
+
7
+ list(): EventRecord[] {
8
+ return this.store.list();
9
+ }
10
+
11
+ add(
12
+ name: string,
13
+ payload: {
14
+ message: string;
15
+ },
16
+ ): EventRecord {
17
+ return this.store.add(name, payload);
18
+ }
19
+ }
@@ -0,0 +1,42 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ export interface EventRecord {
4
+ id: string;
5
+ name: string;
6
+ payload: {
7
+ message: string;
8
+ };
9
+ receivedAt: Date;
10
+ }
11
+
12
+ const MAX_EVENTS = 100;
13
+
14
+ export class EventsStore {
15
+ private readonly events: EventRecord[] = [];
16
+
17
+ add(
18
+ name: string,
19
+ payload: {
20
+ message: string;
21
+ },
22
+ ): EventRecord {
23
+ const entry: EventRecord = {
24
+ id: randomUUID(),
25
+ name,
26
+ payload,
27
+ receivedAt: new Date(),
28
+ };
29
+
30
+ this.events.unshift(entry);
31
+
32
+ if (this.events.length > MAX_EVENTS) {
33
+ this.events.length = MAX_EVENTS;
34
+ }
35
+
36
+ return entry;
37
+ }
38
+
39
+ list(): EventRecord[] {
40
+ return [...this.events];
41
+ }
42
+ }
@@ -0,0 +1,11 @@
1
+ import { createToken } from "@trinacria/core";
2
+ import type { EventProvider } from "@trinacria/events";
3
+ import { EventsController } from "./events.controller";
4
+ import { EventsService } from "./events.service";
5
+ import { EventsStore } from "./events.store";
6
+
7
+ export const EVENTS_STORE = createToken<EventsStore>("EVENTS_STORE");
8
+ export const EVENTS_SERVICE = createToken<EventsService>("EVENTS_SERVICE");
9
+ export const EVENTS_CONTROLLER =
10
+ createToken<EventsController>("EVENTS_CONTROLLER");
11
+ export const EVENTS_PROVIDER = createToken<EventProvider>("EVENTS_PROVIDER");
@@ -0,0 +1,12 @@
1
+ /** @type {import('@trinacria/cli').TrinacriaConfig} */
2
+
3
+ const config = {
4
+ entry: "src/main.ts",
5
+ outDir: "dist",
6
+ watchDir: "src",
7
+ env: "development",
8
+ crashLoopWindowMs: 15_000,
9
+ maxConsecutiveCrashRestarts: 3,
10
+ };
11
+
12
+ export default config;
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "types": ["node"],
4
+ "target": "ES2022",
5
+ "module": "CommonJS",
6
+ "strict": true,
7
+ "declaration": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "rootDir": "src",
11
+ "outDir": "dist"
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,17 @@
1
+ # ------------------------------------------------------------
2
+ # Local app runtime (used by `npm run dev -w api-mongoose-mongodb`)
3
+ # ------------------------------------------------------------
4
+ ENV=development
5
+ HOST=127.0.0.1
6
+ PORT=4002
7
+ OPENAPI_ENABLED=true
8
+ CORS_ALLOWED_ORIGINS=
9
+ DATABASE_URL="mongodb://127.0.0.1:27017/trinacria_example"
10
+
11
+ # ------------------------------------------------------------
12
+ # Docker runtime (used by `docker compose ... --env-file .../.env`)
13
+ # ------------------------------------------------------------
14
+ DOCKER_MONGO_INITDB_DATABASE=trinacria_example
15
+ DOCKER_MONGO_PORT=27017
16
+ DOCKER_API_PORT=4002
17
+ DOCKER_DATABASE_URL="mongodb://mongo:27017/trinacria_example"
@@ -0,0 +1,18 @@
1
+ FROM node:24-bookworm-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package.json package-lock.json tsconfig.base.json ./
6
+ COPY packages ./packages
7
+ COPY apps/api-mongoose-mongodb ./apps/api-mongoose-mongodb
8
+
9
+ RUN npm ci
10
+ RUN npm run build:packages
11
+ RUN npm run build -w api-mongoose-mongodb
12
+
13
+ ENV HOST=0.0.0.0
14
+ ENV PORT=4002
15
+
16
+ EXPOSE 4002
17
+
18
+ CMD ["npm", "run", "start", "-w", "api-mongoose-mongodb"]
@@ -0,0 +1,98 @@
1
+ # API Example: Mongoose + MongoDB
2
+
3
+ A minimal API-only example using Trinacria, Mongoose, and MongoDB.
4
+
5
+ ## Scope
6
+
7
+ This app is intentionally a base template:
8
+
9
+ - no authentication (no JWT/session/password flow)
10
+ - no authorization roles/policies
11
+ - no background jobs
12
+
13
+ Use this as a starting point for integration examples.
14
+
15
+ ## Endpoints
16
+
17
+ - `GET /health`
18
+ - `GET /users`
19
+ - `POST /users`
20
+ - `GET /openapi.json`
21
+ - `GET /docs` (Swagger UI)
22
+
23
+ ## Built-in middleware enabled
24
+
25
+ - `requestId`
26
+ - `requestLogger`
27
+ - `cors`
28
+ - `rateLimit`
29
+ - `requestTimeout`
30
+ - `securityHeaders`
31
+
32
+ ## First steps (local)
33
+
34
+ 1. Install dependencies from the monorepo root:
35
+
36
+ ```bash
37
+ npm install
38
+ ```
39
+
40
+ 2. Copy the environment file:
41
+
42
+ ```bash
43
+ cp apps/api-mongoose-mongodb/.env.example apps/api-mongoose-mongodb/.env.development
44
+ ```
45
+
46
+ 3. Make sure MongoDB is running locally and matches `DATABASE_URL`.
47
+
48
+ 4. Start the API:
49
+
50
+ ```bash
51
+ npm run dev -w api-mongoose-mongodb
52
+ ```
53
+
54
+ 5. Verify the app is running:
55
+
56
+ ```bash
57
+ curl http://127.0.0.1:4002/health
58
+ ```
59
+
60
+ ## Create your first user
61
+
62
+ ```bash
63
+ curl -X POST http://127.0.0.1:4002/users \
64
+ -H 'content-type: application/json' \
65
+ -d '{"name":"Mario Rossi","email":"mario@example.com"}'
66
+ ```
67
+
68
+ Then list users:
69
+
70
+ ```bash
71
+ curl http://127.0.0.1:4002/users
72
+ ```
73
+
74
+ ## Docker quick start
75
+
76
+ 1. Create the Docker env file from the same template:
77
+
78
+ ```bash
79
+ cp apps/api-mongoose-mongodb/.env.example apps/api-mongoose-mongodb/.env
80
+ ```
81
+
82
+ 2. Edit the Docker section in `apps/api-mongoose-mongodb/.env` if you want custom DB name/ports.
83
+
84
+ 3. Start MongoDB + API using that `.env` file:
85
+
86
+ ```bash
87
+ docker compose \
88
+ --env-file apps/api-mongoose-mongodb/.env \
89
+ -f apps/api-mongoose-mongodb/docker-compose.yml \
90
+ up --build
91
+ ```
92
+
93
+ The API will be available at `http://127.0.0.1:${DOCKER_API_PORT}` (default `4002`).
94
+
95
+ Compose services:
96
+
97
+ - `mongo`: MongoDB 7
98
+ - `api`: this Trinacria API (built from this monorepo)
@@ -0,0 +1,34 @@
1
+ services:
2
+ mongo:
3
+ image: mongo:7
4
+ environment:
5
+ MONGO_INITDB_DATABASE: ${DOCKER_MONGO_INITDB_DATABASE}
6
+ ports:
7
+ - "${DOCKER_MONGO_PORT}:27017"
8
+ volumes:
9
+ - trinacria_api_mongo_data:/data/db
10
+ healthcheck:
11
+ test:
12
+ ["CMD", "mongosh", "--quiet", "--eval", "db.runCommand({ ping: 1 }).ok"]
13
+ interval: 5s
14
+ timeout: 5s
15
+ retries: 20
16
+
17
+ api:
18
+ build:
19
+ context: ../..
20
+ dockerfile: apps/api-mongoose-mongodb/Dockerfile
21
+ environment:
22
+ ENV: development
23
+ HOST: 0.0.0.0
24
+ PORT: 4002
25
+ OPENAPI_ENABLED: true
26
+ DATABASE_URL: ${DOCKER_DATABASE_URL}
27
+ ports:
28
+ - "${DOCKER_API_PORT}:4002"
29
+ depends_on:
30
+ mongo:
31
+ condition: service_healthy
32
+
33
+ volumes:
34
+ trinacria_api_mongo_data:
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "api-mongoose-mongodb",
3
+ "private": true,
4
+ "type": "commonjs",
5
+ "scripts": {
6
+ "start": "node ../../packages/cli/dist/index.js start",
7
+ "build": "node ../../packages/cli/dist/index.js build",
8
+ "dev": "node ../../packages/cli/dist/index.js dev"
9
+ },
10
+ "dependencies": {
11
+ "@trinacria/cli": "*",
12
+ "@trinacria/core": "*",
13
+ "@trinacria/http": "*",
14
+ "@trinacria/schema": "*",
15
+ "mongoose": "^9.2.2"
16
+ }
17
+ }
@@ -0,0 +1,92 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createToken } from "@trinacria/core";
4
+ import {
5
+ formatValidationError,
6
+ Infer,
7
+ s,
8
+ ValidationError,
9
+ } from "@trinacria/schema";
10
+
11
+ export const configSchema = s.object({
12
+ ENV: s.enum(["development", "staging", "production"]).default("development"),
13
+ PORT: s.number({ coerce: true, int: true, min: 1, max: 65535 }).default(4002),
14
+ HOST: s.string().default("127.0.0.1"),
15
+ DATABASE_URL: s.string(),
16
+ OPENAPI_ENABLED: s.boolean({ coerce: true }).default(true),
17
+ CORS_ALLOWED_ORIGINS: s.array(s.string(), { coerce: true }).default([]),
18
+ });
19
+
20
+ export type ConfigModel = Infer<typeof configSchema>;
21
+ export const CONFIG_SERVICE = createToken<ConfigService>("CONFIG_SERVICE");
22
+
23
+ export class ConfigService {
24
+ private readonly config: ConfigModel;
25
+
26
+ constructor() {
27
+ this.loadEnvFile();
28
+
29
+ try {
30
+ this.config = configSchema.parse(process.env);
31
+ } catch (error) {
32
+ if (error instanceof ValidationError) {
33
+ throw new Error(
34
+ formatValidationError(error, {
35
+ prefix: "Invalid environment configuration:",
36
+ }),
37
+ );
38
+ }
39
+
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ private loadEnvFile() {
45
+ const currentEnv = process.env.ENV || "development";
46
+ const fileNames = [".env", `.env.${currentEnv}`];
47
+ const roots = [
48
+ process.cwd(),
49
+ path.join(process.cwd(), "apps/api-mongoose-mongodb"),
50
+ ];
51
+
52
+ fileNames.forEach((fileName) => {
53
+ roots.forEach((root) => {
54
+ const filePath = path.join(root, fileName);
55
+ if (!fs.existsSync(filePath)) {
56
+ return;
57
+ }
58
+
59
+ const data = fs.readFileSync(filePath, "utf8");
60
+ data.split("\n").forEach((line) => {
61
+ const trimmed = line.trim();
62
+ if (!trimmed || trimmed.startsWith("#")) {
63
+ return;
64
+ }
65
+
66
+ const separatorIndex = trimmed.indexOf("=");
67
+ if (separatorIndex === -1) {
68
+ return;
69
+ }
70
+
71
+ const key = trimmed.slice(0, separatorIndex).trim();
72
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
73
+ const value =
74
+ (rawValue.startsWith('"') && rawValue.endsWith('"')) ||
75
+ (rawValue.startsWith("'") && rawValue.endsWith("'"))
76
+ ? rawValue.slice(1, -1)
77
+ : rawValue;
78
+
79
+ process.env[key] = value;
80
+ });
81
+ });
82
+ });
83
+ }
84
+
85
+ get<K extends keyof ConfigModel>(key: K): ConfigModel[K] {
86
+ return this.config[key];
87
+ }
88
+
89
+ getAll(): ConfigModel {
90
+ return this.config;
91
+ }
92
+ }
@@ -0,0 +1,28 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>API Mongoose MongoDB Docs</title>
7
+ <link
8
+ rel="stylesheet"
9
+ href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"
10
+ />
11
+ </head>
12
+ <body>
13
+ <div id="swagger-ui"></div>
14
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
15
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"></script>
16
+ <script>
17
+ window.ui = SwaggerUIBundle({
18
+ url: "/openapi.json",
19
+ dom_id: "#swagger-ui",
20
+ deepLinking: true,
21
+ tryItOutEnabled: true,
22
+ persistAuthorization: true,
23
+ presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
24
+ layout: "StandaloneLayout",
25
+ });
26
+ </script>
27
+ </body>
28
+ </html>
@@ -0,0 +1,79 @@
1
+ import { createToken } from "@trinacria/core";
2
+ import { HttpController, response } from "@trinacria/http";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import type { ConfigService } from "../../config.service";
6
+
7
+ export const SWAGGER_DOCS_CONTROLLER = createToken<SwaggerDocsController>(
8
+ "SWAGGER_DOCS_CONTROLLER",
9
+ );
10
+
11
+ export class SwaggerDocsController extends HttpController {
12
+ constructor(private readonly config: ConfigService) {
13
+ super();
14
+ }
15
+
16
+ routes() {
17
+ return this.router()
18
+ .get("/docs", this.renderDocs, {
19
+ docs: {
20
+ excludeFromOpenApi: true,
21
+ },
22
+ })
23
+ .build();
24
+ }
25
+
26
+ async renderDocs() {
27
+ if (!this.config.get("OPENAPI_ENABLED")) {
28
+ return response({ message: "OpenAPI is disabled" }, { status: 404 });
29
+ }
30
+
31
+ return response(await readSwaggerHtml(), {
32
+ headers: {
33
+ "content-type": "text/html; charset=utf-8",
34
+ "content-security-policy": docsContentSecurityPolicy(),
35
+ },
36
+ });
37
+ }
38
+ }
39
+
40
+ let cachedSwaggerHtml: string | undefined;
41
+
42
+ async function readSwaggerHtml(): Promise<string> {
43
+ if (cachedSwaggerHtml) {
44
+ return cachedSwaggerHtml;
45
+ }
46
+
47
+ const candidates = [
48
+ path.resolve(process.cwd(), "src/global/controllers/swagger/docs.html"),
49
+ path.resolve(
50
+ process.cwd(),
51
+ "apps/api-mongoose-mongodb/src/global/controllers/swagger/docs.html",
52
+ ),
53
+ ];
54
+
55
+ for (const filePath of candidates) {
56
+ try {
57
+ cachedSwaggerHtml = await fs.readFile(filePath, "utf8");
58
+ return cachedSwaggerHtml;
59
+ } catch {
60
+ // keep trying next candidate
61
+ }
62
+ }
63
+
64
+ throw new Error("Swagger docs HTML template not found.");
65
+ }
66
+
67
+ function docsContentSecurityPolicy(): string {
68
+ return [
69
+ "default-src 'self'",
70
+ "base-uri 'self'",
71
+ "object-src 'none'",
72
+ "frame-ancestors 'none'",
73
+ "script-src 'self' 'unsafe-inline' https://unpkg.com",
74
+ "style-src 'self' 'unsafe-inline' https://unpkg.com",
75
+ "img-src 'self' data: blob:",
76
+ "font-src 'self' data: https://unpkg.com",
77
+ "connect-src 'self'",
78
+ ].join("; ");
79
+ }
@@ -0,0 +1,18 @@
1
+ import { factoryProvider, type Token } from "@trinacria/core";
2
+ import { type Model, type Schema } from "mongoose";
3
+ import { MONGOOSE_SERVICE, type MongooseService } from "./mongoose.service";
4
+
5
+ export function createMongooseSchemaProvider<
6
+ TEntity,
7
+ TModel extends Model<TEntity>,
8
+ >(token: Token<TModel>, schemaName: string, schema: Schema<TEntity>) {
9
+ return factoryProvider(
10
+ token,
11
+ (mongooseService: MongooseService) => {
12
+ const connection = mongooseService.getConnection();
13
+ return ((connection.models[schemaName] as TModel | undefined) ??
14
+ connection.model<TEntity>(schemaName, schema)) as TModel;
15
+ },
16
+ [MONGOOSE_SERVICE],
17
+ );
18
+ }