@into-cps-association/logger-ms 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 (79) hide show
  1. package/LICENSE.md +3 -0
  2. package/README.md +198 -0
  3. package/dist/src/app.controller.d.ts +12 -0
  4. package/dist/src/app.controller.js +53 -0
  5. package/dist/src/app.controller.js.map +1 -0
  6. package/dist/src/app.module.d.ts +2 -0
  7. package/dist/src/app.module.js +44 -0
  8. package/dist/src/app.module.js.map +1 -0
  9. package/dist/src/config/certificates.d.ts +5 -0
  10. package/dist/src/config/certificates.js +47 -0
  11. package/dist/src/config/certificates.js.map +1 -0
  12. package/dist/src/config/cli.d.ts +1 -0
  13. package/dist/src/config/cli.js +45 -0
  14. package/dist/src/config/cli.js.map +1 -0
  15. package/dist/src/config/config.interface.d.ts +17 -0
  16. package/dist/src/config/config.interface.js +2 -0
  17. package/dist/src/config/config.interface.js.map +1 -0
  18. package/dist/src/config/config.service.d.ts +21 -0
  19. package/dist/src/config/config.service.js +275 -0
  20. package/dist/src/config/config.service.js.map +1 -0
  21. package/dist/src/config/cors.d.ts +7 -0
  22. package/dist/src/config/cors.js +29 -0
  23. package/dist/src/config/cors.js.map +1 -0
  24. package/dist/src/config/util.d.ts +1 -0
  25. package/dist/src/config/util.js +5 -0
  26. package/dist/src/config/util.js.map +1 -0
  27. package/dist/src/dto/log-event.dto.d.ts +37 -0
  28. package/dist/src/dto/log-event.dto.js +125 -0
  29. package/dist/src/dto/log-event.dto.js.map +1 -0
  30. package/dist/src/log-event-validation.pipe.d.ts +4 -0
  31. package/dist/src/log-event-validation.pipe.js +23 -0
  32. package/dist/src/log-event-validation.pipe.js.map +1 -0
  33. package/dist/src/logger-auth.guard.d.ts +7 -0
  34. package/dist/src/logger-auth.guard.js +46 -0
  35. package/dist/src/logger-auth.guard.js.map +1 -0
  36. package/dist/src/logs/logs.module.d.ts +2 -0
  37. package/dist/src/logs/logs.module.js +19 -0
  38. package/dist/src/logs/logs.module.js.map +1 -0
  39. package/dist/src/logs/logs.service.d.ts +29 -0
  40. package/dist/src/logs/logs.service.js +201 -0
  41. package/dist/src/logs/logs.service.js.map +1 -0
  42. package/dist/src/main.d.ts +12 -0
  43. package/dist/src/main.js +59 -0
  44. package/dist/src/main.js.map +1 -0
  45. package/dist/src/socket-address-throttler.guard.d.ts +12 -0
  46. package/dist/src/socket-address-throttler.guard.js +10 -0
  47. package/dist/src/socket-address-throttler.guard.js.map +1 -0
  48. package/dist/src/validation.pipe.d.ts +8 -0
  49. package/dist/src/validation.pipe.js +20 -0
  50. package/dist/src/validation.pipe.js.map +1 -0
  51. package/dist/test/e2e/app.e2e.spec.d.ts +1 -0
  52. package/dist/test/e2e/app.e2e.spec.js +276 -0
  53. package/dist/test/e2e/app.e2e.spec.js.map +1 -0
  54. package/dist/test/unit/certificates.spec.d.ts +1 -0
  55. package/dist/test/unit/certificates.spec.js +46 -0
  56. package/dist/test/unit/certificates.spec.js.map +1 -0
  57. package/dist/test/unit/cli.spec.d.ts +1 -0
  58. package/dist/test/unit/cli.spec.js +100 -0
  59. package/dist/test/unit/cli.spec.js.map +1 -0
  60. package/dist/test/unit/config.service.spec.d.ts +1 -0
  61. package/dist/test/unit/config.service.spec.js +189 -0
  62. package/dist/test/unit/config.service.spec.js.map +1 -0
  63. package/dist/test/unit/cors.spec.d.ts +1 -0
  64. package/dist/test/unit/cors.spec.js +41 -0
  65. package/dist/test/unit/cors.spec.js.map +1 -0
  66. package/dist/test/unit/logger-auth.guard.spec.d.ts +1 -0
  67. package/dist/test/unit/logger-auth.guard.spec.js +40 -0
  68. package/dist/test/unit/logger-auth.guard.spec.js.map +1 -0
  69. package/dist/test/unit/logs.service.spec.d.ts +1 -0
  70. package/dist/test/unit/logs.service.spec.js +246 -0
  71. package/dist/test/unit/logs.service.spec.js.map +1 -0
  72. package/dist/test/unit/socket-address-throttler.guard.spec.d.ts +1 -0
  73. package/dist/test/unit/socket-address-throttler.guard.spec.js +18 -0
  74. package/dist/test/unit/socket-address-throttler.guard.spec.js.map +1 -0
  75. package/dist/test/unit/validation.pipe.spec.d.ts +1 -0
  76. package/dist/test/unit/validation.pipe.spec.js +132 -0
  77. package/dist/test/unit/validation.pipe.spec.js.map +1 -0
  78. package/dist/tsconfig.tsbuildinfo +1 -0
  79. package/package.json +80 -0
@@ -0,0 +1,29 @@
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import Config from '../config/config.service.js';
3
+ import { LogEventDto } from '../dto/log-event.dto.js';
4
+ export default class LogsService implements OnModuleDestroy {
5
+ private readonly logger;
6
+ private readonly logFilePath;
7
+ private readonly maxLogBytes;
8
+ private readonly retentionFiles;
9
+ private writeStream;
10
+ private streamErrorHandler;
11
+ private writeQueue;
12
+ private initialized;
13
+ private initializePromise;
14
+ private currentLogBytes;
15
+ constructor(config: Config);
16
+ appendEvent(event: LogEventDto): Promise<void>;
17
+ onModuleDestroy(): Promise<void>;
18
+ private closeWriteStream;
19
+ private ensureReady;
20
+ private initialize;
21
+ private attachStreamErrorHandler;
22
+ private detachStreamErrorHandler;
23
+ private handleStreamError;
24
+ private ensureNewlineAtEnd;
25
+ private writeLine;
26
+ private rotateIfNeeded;
27
+ private rotateLogFiles;
28
+ private renameIfPresent;
29
+ }
@@ -0,0 +1,201 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var LogsService_1;
11
+ import { createWriteStream, existsSync } from 'node:fs';
12
+ import { appendFile, mkdir, open, rename, rm, stat } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { Injectable, Logger } from '@nestjs/common';
15
+ import Config from '../config/config.service.js';
16
+ function endWriteStream(stream) {
17
+ return new Promise((resolve, reject) => {
18
+ stream.end((error) => {
19
+ if (error !== undefined && error !== null) {
20
+ reject(error);
21
+ return;
22
+ }
23
+ resolve();
24
+ });
25
+ });
26
+ }
27
+ function writeToStream(stream, line) {
28
+ return new Promise((resolve, reject) => {
29
+ const onError = (error) => {
30
+ stream.off('error', onError);
31
+ reject(error);
32
+ };
33
+ stream.on('error', onError);
34
+ stream.write(line, 'utf8', (error) => {
35
+ stream.off('error', onError);
36
+ if (error !== null && error !== undefined) {
37
+ reject(error);
38
+ return;
39
+ }
40
+ resolve();
41
+ });
42
+ });
43
+ }
44
+ let LogsService = LogsService_1 = class LogsService {
45
+ logger = new Logger(LogsService_1.name);
46
+ logFilePath;
47
+ maxLogBytes;
48
+ retentionFiles;
49
+ writeStream = null;
50
+ streamErrorHandler = null;
51
+ writeQueue = Promise.resolve();
52
+ initialized = false;
53
+ initializePromise = null;
54
+ currentLogBytes = 0;
55
+ constructor(config) {
56
+ this.logFilePath = config.getLogFilePath();
57
+ this.maxLogBytes = config.getLogMaxBytes();
58
+ this.retentionFiles = config.getLogRetentionFiles();
59
+ }
60
+ async appendEvent(event) {
61
+ await this.ensureReady();
62
+ const line = `${JSON.stringify(event)}\n`;
63
+ this.writeQueue = this.writeQueue
64
+ .catch(() => undefined)
65
+ .then(() => this.writeLine(line));
66
+ return this.writeQueue;
67
+ }
68
+ async onModuleDestroy() {
69
+ try {
70
+ await this.writeQueue;
71
+ }
72
+ finally {
73
+ await this.closeWriteStream();
74
+ }
75
+ }
76
+ async closeWriteStream() {
77
+ const stream = this.writeStream;
78
+ if (stream !== null) {
79
+ this.detachStreamErrorHandler(stream);
80
+ await endWriteStream(stream);
81
+ this.writeStream = null;
82
+ }
83
+ this.initialized = false;
84
+ this.currentLogBytes = 0;
85
+ }
86
+ async ensureReady() {
87
+ if (this.initialized) {
88
+ return;
89
+ }
90
+ if (this.initializePromise !== null) {
91
+ return this.initializePromise;
92
+ }
93
+ this.initializePromise = this.initialize();
94
+ try {
95
+ await this.initializePromise;
96
+ }
97
+ finally {
98
+ this.initializePromise = null;
99
+ }
100
+ }
101
+ async initialize() {
102
+ await mkdir(path.dirname(this.logFilePath), { recursive: true });
103
+ this.currentLogBytes = await this.ensureNewlineAtEnd();
104
+ this.writeStream = createWriteStream(this.logFilePath, {
105
+ flags: 'a',
106
+ encoding: 'utf8',
107
+ mode: 0o600,
108
+ });
109
+ this.attachStreamErrorHandler(this.writeStream);
110
+ this.initialized = true;
111
+ }
112
+ attachStreamErrorHandler(stream) {
113
+ const handler = (error) => this.handleStreamError(error, stream);
114
+ stream.on('error', handler);
115
+ this.streamErrorHandler = handler;
116
+ }
117
+ detachStreamErrorHandler(stream) {
118
+ if (this.streamErrorHandler === null)
119
+ return;
120
+ stream.off('error', this.streamErrorHandler);
121
+ this.streamErrorHandler = null;
122
+ }
123
+ handleStreamError(error, stream) {
124
+ this.logger.error('Workflow log stream failed', error.stack);
125
+ if (this.writeStream !== stream)
126
+ return;
127
+ this.detachStreamErrorHandler(stream);
128
+ this.writeStream = null;
129
+ this.initialized = false;
130
+ stream.destroy();
131
+ }
132
+ async ensureNewlineAtEnd() {
133
+ if (!existsSync(this.logFilePath)) {
134
+ return 0;
135
+ }
136
+ const fileStat = await stat(this.logFilePath);
137
+ if (fileStat.size === 0) {
138
+ return 0;
139
+ }
140
+ const handle = await open(this.logFilePath, 'r');
141
+ const buffer = Buffer.alloc(1);
142
+ try {
143
+ await handle.read(buffer, 0, 1, fileStat.size - 1);
144
+ }
145
+ finally {
146
+ await handle.close();
147
+ }
148
+ if (buffer[0] !== 0x0a) {
149
+ await appendFile(this.logFilePath, '\n', 'utf8');
150
+ return fileStat.size + 1;
151
+ }
152
+ return fileStat.size;
153
+ }
154
+ async writeLine(line) {
155
+ const lineBytes = Buffer.byteLength(line, 'utf8');
156
+ await this.rotateIfNeeded(lineBytes);
157
+ if (this.writeStream === null) {
158
+ return Promise.reject(new Error('Logger write stream is not initialized'));
159
+ }
160
+ const stream = this.writeStream;
161
+ try {
162
+ await writeToStream(stream, line);
163
+ this.currentLogBytes += lineBytes;
164
+ }
165
+ catch (error) {
166
+ this.handleStreamError(error, stream);
167
+ throw error;
168
+ }
169
+ }
170
+ async rotateIfNeeded(incomingBytes) {
171
+ if (this.currentLogBytes + incomingBytes <= this.maxLogBytes)
172
+ return;
173
+ await this.closeWriteStream();
174
+ await this.rotateLogFiles();
175
+ await this.initialize();
176
+ }
177
+ async rotateLogFiles() {
178
+ for (let index = this.retentionFiles - 1; index >= 1; index -= 1) {
179
+ const source = `${this.logFilePath}.${index}`;
180
+ const target = `${this.logFilePath}.${index + 1}`;
181
+ await rm(target, { force: true });
182
+ await this.renameIfPresent(source, target);
183
+ }
184
+ await this.renameIfPresent(this.logFilePath, `${this.logFilePath}.1`);
185
+ }
186
+ async renameIfPresent(source, target) {
187
+ try {
188
+ await rename(source, target);
189
+ }
190
+ catch (error) {
191
+ if (error.code !== 'ENOENT')
192
+ throw error;
193
+ }
194
+ }
195
+ };
196
+ LogsService = LogsService_1 = __decorate([
197
+ Injectable(),
198
+ __metadata("design:paramtypes", [Config])
199
+ ], LogsService);
200
+ export default LogsService;
201
+ //# sourceMappingURL=logs.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.service.js","sourceRoot":"","sources":["../../../src/logs/logs.service.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAmB,MAAM,gBAAgB,CAAC;AACrE,OAAO,MAAM,MAAM,6BAA6B,CAAC;AAMjD,SAAS,cAAc,CAAC,MAAsB;IAC5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAoB,EAAE,EAAE;YAClC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC1C,MAAM,CAAC,KAAK,CAAC,CAAC;gBACd,OAAO;YACT,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,MAAsB,EAAE,IAAY;IACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,MAAM,CAAC,KAAK,CAAC,CAAC;gBACd,OAAO;YACT,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAGc,IAAM,WAAW,mBAAjB,MAAM,WAAW;IACb,MAAM,GAAG,IAAI,MAAM,CAAC,aAAW,CAAC,IAAI,CAAC,CAAC;IACtC,WAAW,CAAS;IACpB,WAAW,CAAS;IACpB,cAAc,CAAS;IAChC,WAAW,GAA0B,IAAI,CAAC;IAC1C,kBAAkB,GAA8B,IAAI,CAAC;IACrD,UAAU,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9C,WAAW,GAAG,KAAK,CAAC;IACpB,iBAAiB,GAAyB,IAAI,CAAC;IAC/C,eAAe,GAAG,CAAC,CAAC;IAE5B,YAAY,MAAc;QACxB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3C,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,oBAAoB,EAAE,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAkB;QAClC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU;aAC9B,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;aACtB,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,UAAU,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;QAChC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,WAAW;QACvB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,iBAAiB,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,iBAAiB,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,eAAe,GAAG,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACvD,IAAI,CAAC,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,WAAW,EAAE;YACrD,KAAK,EAAE,GAAG;YACV,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;QACH,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAEO,wBAAwB,CAAC,MAAsB;QACrD,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACxE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;IACpC,CAAC;IAEO,wBAAwB,CAAC,MAAsB;QACrD,IAAI,IAAI,CAAC,kBAAkB,KAAK,IAAI;YAAE,OAAO;QAC7C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC7C,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;IACjC,CAAC;IAEO,iBAAiB,CAAC,KAAY,EAAE,MAAsB;QAC5D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7D,IAAI,IAAI,CAAC,WAAW,KAAK,MAAM;YAAE,OAAO;QACxC,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAEO,KAAK,CAAC,kBAAkB;QAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,CAAC;QACX,CAAC;QAGD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACvB,MAAM,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YACjD,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,IAAY;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CAAC,wCAAwC,CAAC,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,eAAe,IAAI,SAAS,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,iBAAiB,CAAC,KAAc,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,aAAqB;QAChD,IAAI,IAAI,CAAC,eAAe,GAAG,aAAa,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QACrE,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9B,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC5B,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;IAC1B,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACjE,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,KAAK,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAClC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;IACxE,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,MAAc,EAAE,MAAc;QAC1D,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;QACtE,CAAC;IACH,CAAC;CACF,CAAA;AAhKoB,WAAW;IAD/B,UAAU,EAAE;qCAaS,MAAM;GAZP,WAAW,CAgK/B;eAhKoB,WAAW"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { INestApplication, NestApplicationOptions } from '@nestjs/common';
3
+ import { json } from 'express';
4
+ import Config from './config/config.service.js';
5
+ type BodyParserApp = {
6
+ use: (middleware: ReturnType<typeof json>) => void;
7
+ };
8
+ type LoggerAppOptions = Omit<NestApplicationOptions, 'bodyParser'>;
9
+ export declare function registerBodyParser(app: BodyParserApp, config: Config): void;
10
+ export declare function configureLoggerApp(app: INestApplication, config: Config): void;
11
+ export declare function createLoggerApp(config: Config, options?: LoggerAppOptions): Promise<INestApplication>;
12
+ export {};
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { NestFactory } from '@nestjs/core';
3
+ import { json } from 'express';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { pathToFileURL } from 'node:url';
6
+ import AppModule from './app.module.js';
7
+ import Config from './config/config.service.js';
8
+ import resolveConfigPath from './config/cli.js';
9
+ import { ensureCertificates } from './config/certificates.js';
10
+ import { buildCorsOptions } from './config/cors.js';
11
+ export function registerBodyParser(app, config) {
12
+ app.use(json({
13
+ limit: `${config.getMaxPayloadBytes()}b`,
14
+ type: ['application/json', 'text/plain'],
15
+ }));
16
+ }
17
+ export function configureLoggerApp(app, config) {
18
+ app.enableCors(buildCorsOptions(config.getCorsAllowOrigin(), config.getCorsAllowCredentials()));
19
+ registerBodyParser(app, config);
20
+ }
21
+ export async function createLoggerApp(config, options = {}) {
22
+ const app = await NestFactory.create(AppModule, {
23
+ ...options,
24
+ bodyParser: false,
25
+ });
26
+ configureLoggerApp(app, config);
27
+ return app;
28
+ }
29
+ async function buildHttpsOptions(config) {
30
+ if (!config.getTls())
31
+ return undefined;
32
+ const certPaths = await ensureCertificates(config.getCertsDirectory());
33
+ return {
34
+ cert: await readFile(certPaths.certFile),
35
+ key: await readFile(certPaths.keyFile),
36
+ };
37
+ }
38
+ function isEntrypoint() {
39
+ const entrypoint = process.argv[1];
40
+ return (entrypoint !== undefined &&
41
+ import.meta.url === pathToFileURL(entrypoint).href);
42
+ }
43
+ async function bootstrap() {
44
+ const configPath = resolveConfigPath(process.argv);
45
+ if (configPath !== undefined) {
46
+ process.env.LOGGER_CONFIG_PATH = configPath;
47
+ }
48
+ const startupConfig = new Config();
49
+ startupConfig.loadConfig(configPath);
50
+ const httpsOptions = await buildHttpsOptions(startupConfig);
51
+ const app = await createLoggerApp(startupConfig, {
52
+ httpsOptions,
53
+ });
54
+ await app.listen(startupConfig.getPort(), startupConfig.getHostname());
55
+ }
56
+ if (isEntrypoint()) {
57
+ await bootstrap();
58
+ }
59
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { ThrottlerGuard } from '@nestjs/throttler';
2
+ type SocketAddressRequest = {
3
+ [key: string]: unknown;
4
+ socket?: {
5
+ remoteAddress?: string;
6
+ };
7
+ };
8
+ export declare function getSocketTracker(req: SocketAddressRequest): string;
9
+ export default class SocketAddressThrottlerGuard extends ThrottlerGuard {
10
+ protected getTracker(req: Record<string, any>): Promise<string>;
11
+ }
12
+ export {};
@@ -0,0 +1,10 @@
1
+ import { ThrottlerGuard } from '@nestjs/throttler';
2
+ export function getSocketTracker(req) {
3
+ return req.socket?.remoteAddress ?? 'unknown';
4
+ }
5
+ export default class SocketAddressThrottlerGuard extends ThrottlerGuard {
6
+ async getTracker(req) {
7
+ return getSocketTracker(req);
8
+ }
9
+ }
10
+ //# sourceMappingURL=socket-address-throttler.guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-address-throttler.guard.js","sourceRoot":"","sources":["../../src/socket-address-throttler.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AASnD,MAAM,UAAU,gBAAgB,CAAC,GAAyB;IACxD,OAAO,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,OAAO,OAAO,2BAA4B,SAAQ,cAAc;IAC3D,KAAK,CAAC,UAAU,CAAC,GAAwB;QACjD,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ import { PipeTransform } from '@nestjs/common';
2
+ import { ZodType } from 'zod';
3
+ import { LogEventDto } from './dto/log-event.dto.js';
4
+ export default class ZodValidationPipe implements PipeTransform {
5
+ private readonly schema;
6
+ constructor(schema: ZodType<LogEventDto>);
7
+ transform(value: unknown): LogEventDto;
8
+ }
@@ -0,0 +1,20 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ export default class ZodValidationPipe {
3
+ schema;
4
+ constructor(schema) {
5
+ this.schema = schema;
6
+ }
7
+ transform(value) {
8
+ try {
9
+ const parsedValue = this.schema.parse(value);
10
+ return parsedValue;
11
+ }
12
+ catch (error) {
13
+ throw new BadRequestException('Validation Failed', {
14
+ cause: error,
15
+ description: 'Bad Request',
16
+ });
17
+ }
18
+ }
19
+ }
20
+ //# sourceMappingURL=validation.pipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.pipe.js","sourceRoot":"","sources":["../../src/validation.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIpE,MAAM,CAAC,OAAO,OAAO,iBAAiB;IAEP;IAA7B,YAA6B,MAA4B;QAA5B,WAAM,GAAN,MAAM,CAAsB;IAAG,CAAC;IAE7D,SAAS,CAAC,KAAc;QACtB,IAAI,CAAC;YACH,MAAM,WAAW,GAAgB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,mBAAmB,CAAC,mBAAmB,EAAE;gBACjD,KAAK,EAAE,KAAK;gBAGZ,WAAW,EAAE,aAAa;aAC3B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,276 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
2
+ import { HttpStatus } from '@nestjs/common';
3
+ import supertest from 'supertest';
4
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import Config from 'src/config/config.service';
8
+ import { createLoggerApp } from 'src/main';
9
+ const VALID_PAYLOAD_FIXTURES = [
10
+ 'sample.json',
11
+ 'valid-1.json',
12
+ 'valid-2.json',
13
+ 'valid-3.json',
14
+ 'valid-4.json',
15
+ 'valid-5.json',
16
+ 'valid-6.json',
17
+ ];
18
+ const INVALID_PAYLOAD_FIXTURES = [
19
+ 'invalid-1.json',
20
+ 'invalid-2.json',
21
+ 'invalid-3.json',
22
+ 'invalid-4.json',
23
+ 'invalid-5.json',
24
+ ];
25
+ async function readPayloadFixture(fileName) {
26
+ const fixturePath = path.resolve(process.cwd(), 'api', fileName);
27
+ const payload = await readFile(fixturePath, 'utf8');
28
+ return JSON.parse(payload);
29
+ }
30
+ async function createTestApp() {
31
+ const app = await createLoggerApp(new Config(), { logger: false });
32
+ await app.init();
33
+ return app;
34
+ }
35
+ describe('Logger service e2e', () => {
36
+ let app;
37
+ let logFilePath = '';
38
+ let tempDir = '';
39
+ beforeAll(async () => {
40
+ tempDir = await mkdtemp(path.join(os.tmpdir(), 'dtaas-logger-e2e-'));
41
+ logFilePath = path.join(tempDir, 'events.jsonl');
42
+ process.env.LOGGER_CONFIG_PATH = '';
43
+ process.env.LOGGER_TLS = 'false';
44
+ process.env.LOGGER_CERTS_DIR = '';
45
+ process.env.LOGGER_CORS_ALLOW_ORIGIN = '*';
46
+ process.env.LOGGER_LOG_FILE_PATH = logFilePath;
47
+ process.env.LOGGER_MAX_PAYLOAD_BYTES = '65536';
48
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
49
+ delete process.env.LOGGER_AUTH_TOKEN;
50
+ app = await createTestApp();
51
+ });
52
+ afterAll(async () => {
53
+ await app.close();
54
+ delete process.env.LOGGER_CONFIG_PATH;
55
+ delete process.env.LOGGER_TLS;
56
+ delete process.env.LOGGER_CERTS_DIR;
57
+ delete process.env.LOGGER_CORS_ALLOW_ORIGIN;
58
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
59
+ delete process.env.LOGGER_LOG_FILE_PATH;
60
+ delete process.env.LOGGER_MAX_PAYLOAD_BYTES;
61
+ delete process.env.LOGGER_AUTH_TOKEN;
62
+ await rm(tempDir, { recursive: true, force: true });
63
+ });
64
+ it('GET /logger/health returns ok', async () => {
65
+ await supertest(app.getHttpServer())
66
+ .get('/logger/health')
67
+ .expect(HttpStatus.OK)
68
+ .expect({ status: 'ok' });
69
+ });
70
+ it('POST /logger persists valid payload fixtures from api/', async () => {
71
+ const sentPayloads = [];
72
+ for (const fileName of VALID_PAYLOAD_FIXTURES) {
73
+ const payload = await readPayloadFixture(fileName);
74
+ sentPayloads.push(payload);
75
+ await supertest(app.getHttpServer())
76
+ .post('/logger')
77
+ .send(payload)
78
+ .set('Content-Type', 'application/json')
79
+ .expect(HttpStatus.NO_CONTENT);
80
+ }
81
+ const content = await readFile(logFilePath, 'utf8');
82
+ const lines = content.trim().split('\n');
83
+ expect(lines).toHaveLength(VALID_PAYLOAD_FIXTURES.length);
84
+ for (let index = 0; index < lines.length; index += 1) {
85
+ expect(JSON.parse(lines[index])).toEqual(sentPayloads[index]);
86
+ }
87
+ });
88
+ it('POST /logger rejects invalid payload fixtures from api/', async () => {
89
+ for (const fileName of INVALID_PAYLOAD_FIXTURES) {
90
+ const payload = await readPayloadFixture(fileName);
91
+ const response = await supertest(app.getHttpServer())
92
+ .post('/logger')
93
+ .send(payload)
94
+ .set('Content-Type', 'application/json');
95
+ expect(response.status).toBe(HttpStatus.BAD_REQUEST);
96
+ expect(response.body).toEqual({
97
+ message: 'Validation Failed',
98
+ error: 'Bad Request',
99
+ statusCode: 400,
100
+ });
101
+ }
102
+ });
103
+ it('OPTIONS /logger excludes credentials by default', async () => {
104
+ const response = await supertest(app.getHttpServer())
105
+ .options('/logger')
106
+ .set('Origin', 'http://localhost:3000')
107
+ .set('Access-Control-Request-Method', 'POST');
108
+ expect(response.status).toBe(HttpStatus.NO_CONTENT);
109
+ expect(response.headers['access-control-allow-origin']).toBe('http://localhost:3000');
110
+ expect(response.headers['access-control-allow-credentials']).toBeUndefined();
111
+ });
112
+ });
113
+ describe('Logger service e2e with auth token configured', () => {
114
+ let app;
115
+ let tempDir = '';
116
+ beforeAll(async () => {
117
+ tempDir = await mkdtemp(path.join(os.tmpdir(), 'dtaas-logger-e2e-auth-'));
118
+ process.env.LOGGER_CONFIG_PATH = '';
119
+ process.env.LOGGER_TLS = 'false';
120
+ process.env.LOGGER_CERTS_DIR = '';
121
+ process.env.LOGGER_CORS_ALLOW_ORIGIN = '*';
122
+ process.env.LOGGER_LOG_FILE_PATH = path.join(tempDir, 'events.jsonl');
123
+ process.env.LOGGER_MAX_PAYLOAD_BYTES = '65536';
124
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
125
+ process.env.LOGGER_AUTH_TOKEN = 'test-secret-token';
126
+ app = await createTestApp();
127
+ });
128
+ afterAll(async () => {
129
+ await app.close();
130
+ delete process.env.LOGGER_CONFIG_PATH;
131
+ delete process.env.LOGGER_TLS;
132
+ delete process.env.LOGGER_CERTS_DIR;
133
+ delete process.env.LOGGER_CORS_ALLOW_ORIGIN;
134
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
135
+ delete process.env.LOGGER_LOG_FILE_PATH;
136
+ delete process.env.LOGGER_MAX_PAYLOAD_BYTES;
137
+ delete process.env.LOGGER_AUTH_TOKEN;
138
+ await rm(tempDir, { recursive: true, force: true });
139
+ });
140
+ it('rejects POST /logger without a bearer token', async () => {
141
+ const payload = await readPayloadFixture('sample.json');
142
+ const response = await supertest(app.getHttpServer())
143
+ .post('/logger')
144
+ .send(payload)
145
+ .set('Content-Type', 'application/json');
146
+ expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
147
+ });
148
+ it('rejects POST /logger with the wrong bearer token', async () => {
149
+ const payload = await readPayloadFixture('sample.json');
150
+ const response = await supertest(app.getHttpServer())
151
+ .post('/logger')
152
+ .send(payload)
153
+ .set('Content-Type', 'application/json')
154
+ .set('Authorization', 'Bearer wrong-token');
155
+ expect(response.status).toBe(HttpStatus.UNAUTHORIZED);
156
+ });
157
+ it('accepts POST /logger with the configured bearer token', async () => {
158
+ const payload = await readPayloadFixture('sample.json');
159
+ const response = await supertest(app.getHttpServer())
160
+ .post('/logger')
161
+ .send(payload)
162
+ .set('Content-Type', 'application/json')
163
+ .set('Authorization', 'Bearer test-secret-token');
164
+ expect(response.status).toBe(HttpStatus.NO_CONTENT);
165
+ });
166
+ it('GET /logger/health does not require a bearer token', async () => {
167
+ await supertest(app.getHttpServer())
168
+ .get('/logger/health')
169
+ .expect(HttpStatus.OK);
170
+ });
171
+ });
172
+ describe('Logger service e2e with a configured throttle', () => {
173
+ let app;
174
+ let tempDir = '';
175
+ beforeAll(async () => {
176
+ tempDir = await mkdtemp(path.join(os.tmpdir(), 'dtaas-logger-e2e-limit-'));
177
+ process.env.LOGGER_CONFIG_PATH = '';
178
+ process.env.LOGGER_TLS = 'false';
179
+ process.env.LOGGER_CERTS_DIR = '';
180
+ process.env.LOGGER_CORS_ALLOW_ORIGIN = '*';
181
+ process.env.LOGGER_LOG_FILE_PATH = path.join(tempDir, 'events.jsonl');
182
+ process.env.LOGGER_MAX_PAYLOAD_BYTES = '65536';
183
+ process.env.LOGGER_THROTTLE_TTL = '60000';
184
+ process.env.LOGGER_THROTTLE_LIMIT = '1';
185
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
186
+ delete process.env.LOGGER_AUTH_TOKEN;
187
+ app = await createTestApp();
188
+ });
189
+ afterAll(async () => {
190
+ await app.close();
191
+ delete process.env.LOGGER_CONFIG_PATH;
192
+ delete process.env.LOGGER_TLS;
193
+ delete process.env.LOGGER_CERTS_DIR;
194
+ delete process.env.LOGGER_CORS_ALLOW_ORIGIN;
195
+ delete process.env.LOGGER_LOG_FILE_PATH;
196
+ delete process.env.LOGGER_MAX_PAYLOAD_BYTES;
197
+ delete process.env.LOGGER_THROTTLE_TTL;
198
+ delete process.env.LOGGER_THROTTLE_LIMIT;
199
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
200
+ delete process.env.LOGGER_AUTH_TOKEN;
201
+ await rm(tempDir, { recursive: true, force: true });
202
+ });
203
+ it('keeps health available after logger ingest is throttled', async () => {
204
+ const payload = await readPayloadFixture('sample.json');
205
+ await supertest(app.getHttpServer())
206
+ .post('/logger')
207
+ .send(payload)
208
+ .expect(HttpStatus.NO_CONTENT);
209
+ await supertest(app.getHttpServer())
210
+ .post('/logger')
211
+ .send(payload)
212
+ .expect(HttpStatus.TOO_MANY_REQUESTS);
213
+ await supertest(app.getHttpServer())
214
+ .get('/logger/health')
215
+ .expect(HttpStatus.OK)
216
+ .expect({ status: 'ok' });
217
+ });
218
+ });
219
+ describe('Logger service e2e with production body-parser config', () => {
220
+ let app;
221
+ let logFilePath = '';
222
+ let tempDir = '';
223
+ beforeAll(async () => {
224
+ tempDir = await mkdtemp(path.join(os.tmpdir(), 'dtaas-logger-e2e-textplain-'));
225
+ logFilePath = path.join(tempDir, 'events.jsonl');
226
+ process.env.LOGGER_CONFIG_PATH = '';
227
+ process.env.LOGGER_TLS = 'false';
228
+ process.env.LOGGER_CERTS_DIR = '';
229
+ process.env.LOGGER_CORS_ALLOW_ORIGIN = '*';
230
+ process.env.LOGGER_LOG_FILE_PATH = logFilePath;
231
+ process.env.LOGGER_MAX_PAYLOAD_BYTES = '65536';
232
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
233
+ delete process.env.LOGGER_AUTH_TOKEN;
234
+ app = await createTestApp();
235
+ });
236
+ afterAll(async () => {
237
+ await app.close();
238
+ delete process.env.LOGGER_CONFIG_PATH;
239
+ delete process.env.LOGGER_TLS;
240
+ delete process.env.LOGGER_CERTS_DIR;
241
+ delete process.env.LOGGER_CORS_ALLOW_ORIGIN;
242
+ delete process.env.LOGGER_CORS_ALLOW_CREDENTIALS;
243
+ delete process.env.LOGGER_LOG_FILE_PATH;
244
+ delete process.env.LOGGER_MAX_PAYLOAD_BYTES;
245
+ delete process.env.LOGGER_AUTH_TOKEN;
246
+ await rm(tempDir, { recursive: true, force: true });
247
+ });
248
+ it('accepts a JSON body sent with Content-Type: text/plain, as navigator.sendBeacon sends it', async () => {
249
+ const payload = await readPayloadFixture('sample.json');
250
+ const response = await supertest(app.getHttpServer())
251
+ .post('/logger')
252
+ .send(JSON.stringify(payload))
253
+ .set('Content-Type', 'text/plain;charset=UTF-8');
254
+ expect(response.status).toBe(HttpStatus.NO_CONTENT);
255
+ const content = await readFile(logFilePath, 'utf8');
256
+ expect(JSON.parse(content.trim())).toEqual(payload);
257
+ });
258
+ it.each([
259
+ ['application/json', (payload) => payload],
260
+ [
261
+ 'text/plain;charset=UTF-8',
262
+ (payload) => JSON.stringify(payload),
263
+ ],
264
+ ])('rejects oversized %s payloads', async (contentType, body) => {
265
+ const payload = {
266
+ ...(await readPayloadFixture('sample.json')),
267
+ context: { padding: 'x'.repeat(80 * 1024) },
268
+ };
269
+ const response = await supertest(app.getHttpServer())
270
+ .post('/logger')
271
+ .send(body(payload))
272
+ .set('Content-Type', contentType);
273
+ expect(response.status).toBe(HttpStatus.PAYLOAD_TOO_LARGE);
274
+ });
275
+ });
276
+ //# sourceMappingURL=app.e2e.spec.js.map