@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,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,18 @@
1
+ # ------------------------------------------------------------
2
+ # Local app runtime (used by `npm run dev -w api-events-redis`)
3
+ # ------------------------------------------------------------
4
+ ENV=development
5
+ HOST=127.0.0.1
6
+ PORT=4003
7
+ OPENAPI_ENABLED=true
8
+ CORS_ALLOWED_ORIGINS=
9
+ REDIS_URL=redis://127.0.0.1:6379
10
+ REDIS_CHANNEL=trinacria:events:demo
11
+
12
+ # ------------------------------------------------------------
13
+ # Docker runtime (used by `docker compose ... --env-file .../.env`)
14
+ # ------------------------------------------------------------
15
+ DOCKER_REDIS_PORT=6379
16
+ DOCKER_API_PORT=4003
17
+ DOCKER_REDIS_URL=redis://redis:6379
18
+ DOCKER_REDIS_CHANNEL=trinacria:events:demo
@@ -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-events-redis ./apps/api-events-redis
8
+
9
+ RUN npm ci
10
+ RUN npm run build:packages
11
+ RUN npm run build -w api-events-redis
12
+
13
+ ENV HOST=0.0.0.0
14
+ ENV PORT=4003
15
+
16
+ EXPOSE 4003
17
+
18
+ CMD ["npm", "run", "start", "-w", "api-events-redis"]
@@ -0,0 +1,97 @@
1
+ # API Example: Redis Events
2
+
3
+ A minimal API-only example using Trinacria Events with Redis Pub/Sub transport.
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 database persistence for domain data
12
+ - focus only on event publish/consume flow via Redis
13
+
14
+ ## Endpoints
15
+
16
+ - `GET /health`
17
+ - `GET /events`
18
+ - `POST /events/publish`
19
+ - `GET /openapi.json`
20
+ - `GET /docs` (Swagger UI)
21
+
22
+ ## Built-in middleware enabled
23
+
24
+ - `requestId`
25
+ - `requestLogger`
26
+ - `cors`
27
+ - `rateLimit`
28
+ - `requestTimeout`
29
+ - `securityHeaders`
30
+
31
+ ## First steps (local)
32
+
33
+ 1. Install dependencies from the monorepo root:
34
+
35
+ ```bash
36
+ npm install
37
+ ```
38
+
39
+ 2. Copy the environment file:
40
+
41
+ ```bash
42
+ cp apps/api-events-redis/.env.example apps/api-events-redis/.env.development
43
+ ```
44
+
45
+ 3. Make sure Redis is running locally and matches `REDIS_URL`.
46
+
47
+ 4. Start the API:
48
+
49
+ ```bash
50
+ npm run dev -w api-events-redis
51
+ ```
52
+
53
+ 5. Verify the app is running:
54
+
55
+ ```bash
56
+ curl http://127.0.0.1:4003/health
57
+ ```
58
+
59
+ ## Publish your first event
60
+
61
+ ```bash
62
+ curl -X POST http://127.0.0.1:4003/events/publish \
63
+ -H 'content-type: application/json' \
64
+ -d '{"message":"hello redis events"}'
65
+ ```
66
+
67
+ Then list consumed events:
68
+
69
+ ```bash
70
+ curl http://127.0.0.1:4003/events
71
+ ```
72
+
73
+ ## Docker quick start
74
+
75
+ 1. Create the Docker env file from the same template:
76
+
77
+ ```bash
78
+ cp apps/api-events-redis/.env.example apps/api-events-redis/.env
79
+ ```
80
+
81
+ 2. Edit the Docker section in `apps/api-events-redis/.env` if you want custom Redis credentials/ports.
82
+
83
+ 3. Start Redis + API using that `.env` file:
84
+
85
+ ```bash
86
+ docker compose \
87
+ --env-file apps/api-events-redis/.env \
88
+ -f apps/api-events-redis/docker-compose.yml \
89
+ up --build
90
+ ```
91
+
92
+ The API will be available at `http://127.0.0.1:${DOCKER_API_PORT}` (default `4003`).
93
+
94
+ Compose services:
95
+
96
+ - `redis`: Redis 7
97
+ - `api`: this Trinacria API
@@ -0,0 +1,33 @@
1
+ services:
2
+ redis:
3
+ image: redis:7-alpine
4
+ command: ["redis-server", "--appendonly", "yes"]
5
+ ports:
6
+ - "${DOCKER_REDIS_PORT}:6379"
7
+ volumes:
8
+ - trinacria_api_redis_data:/data
9
+ healthcheck:
10
+ test: ["CMD", "redis-cli", "ping"]
11
+ interval: 5s
12
+ timeout: 5s
13
+ retries: 20
14
+
15
+ api:
16
+ build:
17
+ context: ../..
18
+ dockerfile: apps/api-events-redis/Dockerfile
19
+ environment:
20
+ ENV: development
21
+ HOST: 0.0.0.0
22
+ PORT: 4003
23
+ OPENAPI_ENABLED: true
24
+ REDIS_URL: ${DOCKER_REDIS_URL}
25
+ REDIS_CHANNEL: ${DOCKER_REDIS_CHANNEL}
26
+ ports:
27
+ - "${DOCKER_API_PORT}:4003"
28
+ depends_on:
29
+ redis:
30
+ condition: service_healthy
31
+
32
+ volumes:
33
+ trinacria_api_redis_data:
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "api-events-redis",
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/events": "*",
14
+ "@trinacria/http": "*",
15
+ "@trinacria/schema": "*",
16
+ "redis": "^5.10.0"
17
+ }
18
+ }
@@ -0,0 +1,93 @@
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(4003),
14
+ HOST: s.string().default("127.0.0.1"),
15
+ OPENAPI_ENABLED: s.boolean({ coerce: true }).default(true),
16
+ CORS_ALLOWED_ORIGINS: s.array(s.string(), { coerce: true }).default([]),
17
+ REDIS_URL: s.string(),
18
+ REDIS_CHANNEL: s.string().default("trinacria:events:demo"),
19
+ });
20
+
21
+ export type ConfigModel = Infer<typeof configSchema>;
22
+ export const CONFIG_SERVICE = createToken<ConfigService>("CONFIG_SERVICE");
23
+
24
+ export class ConfigService {
25
+ private readonly config: ConfigModel;
26
+
27
+ constructor() {
28
+ this.loadEnvFile();
29
+
30
+ try {
31
+ this.config = configSchema.parse(process.env);
32
+ } catch (error) {
33
+ if (error instanceof ValidationError) {
34
+ throw new Error(
35
+ formatValidationError(error, {
36
+ prefix: "Invalid environment configuration:",
37
+ }),
38
+ );
39
+ }
40
+
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ private loadEnvFile() {
46
+ const currentEnv = process.env.ENV || "development";
47
+ const fileNames = [".env", `.env.${currentEnv}`];
48
+ const roots = [
49
+ process.cwd(),
50
+ path.join(process.cwd(), "apps/api-events-redis"),
51
+ ];
52
+
53
+ fileNames.forEach((fileName) => {
54
+ roots.forEach((root) => {
55
+ const filePath = path.join(root, fileName);
56
+ if (!fs.existsSync(filePath)) {
57
+ return;
58
+ }
59
+
60
+ const data = fs.readFileSync(filePath, "utf8");
61
+ data.split("\n").forEach((line) => {
62
+ const trimmed = line.trim();
63
+ if (!trimmed || trimmed.startsWith("#")) {
64
+ return;
65
+ }
66
+
67
+ const separatorIndex = trimmed.indexOf("=");
68
+ if (separatorIndex === -1) {
69
+ return;
70
+ }
71
+
72
+ const key = trimmed.slice(0, separatorIndex).trim();
73
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
74
+ const value =
75
+ (rawValue.startsWith('"') && rawValue.endsWith('"')) ||
76
+ (rawValue.startsWith("'") && rawValue.endsWith("'"))
77
+ ? rawValue.slice(1, -1)
78
+ : rawValue;
79
+
80
+ process.env[key] = value;
81
+ });
82
+ });
83
+ });
84
+ }
85
+
86
+ get<K extends keyof ConfigModel>(key: K): ConfigModel[K] {
87
+ return this.config[key];
88
+ }
89
+
90
+ getAll(): ConfigModel {
91
+ return this.config;
92
+ }
93
+ }
@@ -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 Events Redis 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-events-redis/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,50 @@
1
+ import { createClient, type RedisClientType } from "redis";
2
+ import { createToken } from "@trinacria/core";
3
+ import type { ConfigService } from "./config.service";
4
+
5
+ export const REDIS_SERVICE = createToken<RedisService>("REDIS_SERVICE");
6
+
7
+ export class RedisService {
8
+ private readonly publisherClient: RedisClientType;
9
+ private readonly subscriberClient: RedisClientType;
10
+
11
+ constructor(config: ConfigService) {
12
+ const redisUrl = config.get("REDIS_URL");
13
+
14
+ this.publisherClient = createClient({
15
+ url: redisUrl,
16
+ });
17
+
18
+ this.subscriberClient = createClient({
19
+ url: redisUrl,
20
+ });
21
+ }
22
+
23
+ async onInit(): Promise<void> {
24
+ if (!this.publisherClient.isOpen) {
25
+ await this.publisherClient.connect();
26
+ }
27
+
28
+ if (!this.subscriberClient.isOpen) {
29
+ await this.subscriberClient.connect();
30
+ }
31
+ }
32
+
33
+ getPublisherClient(): RedisClientType {
34
+ return this.publisherClient;
35
+ }
36
+
37
+ getSubscriberClient(): RedisClientType {
38
+ return this.subscriberClient;
39
+ }
40
+
41
+ async onDestroy(): Promise<void> {
42
+ if (this.publisherClient.isOpen) {
43
+ await this.publisherClient.quit();
44
+ }
45
+
46
+ if (this.subscriberClient.isOpen) {
47
+ await this.subscriberClient.quit();
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,20 @@
1
+ import { TrinacriaApp } from "@trinacria/core";
2
+ import { httpProvider } from "@trinacria/http";
3
+ import { ConfigService, CONFIG_SERVICE } from "./config.service";
4
+ import {
5
+ SWAGGER_DOCS_CONTROLLER,
6
+ SwaggerDocsController,
7
+ } from "./controllers/swagger/swagger-docs.controller";
8
+
9
+ export function registerGlobalControllers(
10
+ app: TrinacriaApp,
11
+ config: ConfigService,
12
+ ): void {
13
+ if (config.get("OPENAPI_ENABLED")) {
14
+ app.registerGlobalProvider(
15
+ httpProvider(SWAGGER_DOCS_CONTROLLER, SwaggerDocsController, [
16
+ CONFIG_SERVICE,
17
+ ]),
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,88 @@
1
+ import { ConsoleLogger, TrinacriaApp, valueProvider } from "@trinacria/core";
2
+ import { createEventsPlugin, RedisEventTransport } from "@trinacria/events";
3
+ import {
4
+ cors,
5
+ createHttpPlugin,
6
+ createSecurityHeadersBuilder,
7
+ rateLimit,
8
+ requestId,
9
+ requestLogger,
10
+ requestTimeout,
11
+ } from "@trinacria/http";
12
+ import { CONFIG_SERVICE, ConfigService } from "./global/config.service";
13
+ import { REDIS_SERVICE, RedisService } from "./global/redis.service";
14
+ import { registerGlobalControllers } from "./global/register-global-controllers";
15
+ import { EventsModule } from "./modules/events/events.module";
16
+
17
+ async function bootstrap() {
18
+ const app = new TrinacriaApp();
19
+ const configService = new ConfigService();
20
+ const config = configService.getAll();
21
+ const eventsLogger = new ConsoleLogger("api-events-redis:events");
22
+ const isProduction = config.ENV === "production";
23
+ const corsOrigins = config.CORS_ALLOWED_ORIGINS;
24
+ const securityHeadersMiddleware = createSecurityHeadersBuilder()
25
+ .preset(config.ENV)
26
+ .trustProxy(false)
27
+ .build();
28
+
29
+ const redisService = new RedisService(configService);
30
+ await redisService.onInit();
31
+
32
+ app.registerGlobalProvider(valueProvider(CONFIG_SERVICE, configService));
33
+ app.registerGlobalProvider(valueProvider(REDIS_SERVICE, redisService));
34
+ registerGlobalControllers(app, configService);
35
+
36
+ app.use(
37
+ createHttpPlugin({
38
+ host: config.HOST,
39
+ port: config.PORT,
40
+ middlewares: [
41
+ requestId(),
42
+ requestLogger({ includeUserAgent: !isProduction }),
43
+ cors({
44
+ origin: corsOrigins.length > 0 ? corsOrigins : "*",
45
+ credentials: true,
46
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
47
+ }),
48
+ rateLimit({
49
+ windowMs: 60_000,
50
+ max: isProduction ? 240 : 2_000,
51
+ trustProxy: false,
52
+ }),
53
+ requestTimeout({ timeoutMs: 15_000 }),
54
+ securityHeadersMiddleware,
55
+ ],
56
+ openApi: config.OPENAPI_ENABLED
57
+ ? {
58
+ enabled: true,
59
+ title: "API Events Redis Example",
60
+ version: "1.0.0",
61
+ }
62
+ : undefined,
63
+ }),
64
+ );
65
+
66
+ app.use(
67
+ createEventsPlugin({
68
+ transport: new RedisEventTransport({
69
+ publisher: redisService.getPublisherClient(),
70
+ subscriber: redisService.getSubscriberClient(),
71
+ channel: config.REDIS_CHANNEL,
72
+ }),
73
+ source: "api-events-redis",
74
+ dispatchLocalOnEmit: false,
75
+ onListenerError: (error, envelope) => {
76
+ eventsLogger.error(`Event listener error: ${envelope.name}`, error);
77
+ },
78
+ }),
79
+ );
80
+
81
+ await app.registerModule(EventsModule);
82
+ await app.start();
83
+ }
84
+
85
+ bootstrap().catch((error) => {
86
+ console.error(error);
87
+ process.exit(1);
88
+ });