mohen 1.0.0
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.
- package/.github/workflows/ci.yml +40 -0
- package/.github/workflows/publish.yml +38 -0
- package/README.md +198 -0
- package/dist/logger.d.ts +72 -0
- package/dist/logger.js +389 -0
- package/example/usage.ts +161 -0
- package/logo.png +0 -0
- package/package.json +40 -0
- package/src/logger.ts +454 -0
- package/test/logger.test.ts +499 -0
- package/test/test-server.ts +52 -0
- package/tsconfig.json +29 -0
- package/vitest.config.ts +13 -0
package/dist/logger.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createLogger = createLogger;
|
|
37
|
+
exports.attachMetadata = attachMetadata;
|
|
38
|
+
exports.attachTrpcMetadata = attachTrpcMetadata;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Core Logger Class
|
|
43
|
+
// ============================================================================
|
|
44
|
+
class UnifiedLogger {
|
|
45
|
+
constructor(filePath, options = {}) {
|
|
46
|
+
this.filePath = path.resolve(filePath);
|
|
47
|
+
this.maxSizeBytes = options.maxSizeBytes ?? 10 * 1024 * 1024; // 10MB default
|
|
48
|
+
this.includeHeaders = options.includeHeaders ?? false;
|
|
49
|
+
this.redactFields = new Set(options.redact ?? ['password', 'token', 'authorization', 'cookie']);
|
|
50
|
+
// Ensure directory exists
|
|
51
|
+
const dir = path.dirname(this.filePath);
|
|
52
|
+
if (!fs.existsSync(dir)) {
|
|
53
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
generateRequestId() {
|
|
57
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
|
|
58
|
+
}
|
|
59
|
+
redact(obj) {
|
|
60
|
+
if (obj === null || obj === undefined)
|
|
61
|
+
return obj;
|
|
62
|
+
if (typeof obj !== 'object')
|
|
63
|
+
return obj;
|
|
64
|
+
if (Array.isArray(obj)) {
|
|
65
|
+
return obj.map((item) => this.redact(item));
|
|
66
|
+
}
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
69
|
+
if (this.redactFields.has(key.toLowerCase())) {
|
|
70
|
+
result[key] = '[REDACTED]';
|
|
71
|
+
}
|
|
72
|
+
else if (typeof value === 'object') {
|
|
73
|
+
result[key] = this.redact(value);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
result[key] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
checkAndRotate() {
|
|
82
|
+
try {
|
|
83
|
+
if (!fs.existsSync(this.filePath))
|
|
84
|
+
return;
|
|
85
|
+
const stats = fs.statSync(this.filePath);
|
|
86
|
+
if (stats.size > this.maxSizeBytes) {
|
|
87
|
+
// Read file, keep last 25% of lines
|
|
88
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
89
|
+
const lines = content.trim().split('\n');
|
|
90
|
+
const keepCount = Math.floor(lines.length * 0.25);
|
|
91
|
+
const newContent = lines.slice(-keepCount).join('\n') + '\n';
|
|
92
|
+
fs.writeFileSync(this.filePath, newContent);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error('Logger rotation error:', err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
write(entry) {
|
|
100
|
+
try {
|
|
101
|
+
this.checkAndRotate();
|
|
102
|
+
const redactedEntry = this.redact(entry);
|
|
103
|
+
const line = JSON.stringify(redactedEntry) + '\n';
|
|
104
|
+
fs.appendFileSync(this.filePath, line);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error('Logger write error:', err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ===========================================================================
|
|
111
|
+
// Express Middleware
|
|
112
|
+
// ===========================================================================
|
|
113
|
+
expressMiddleware() {
|
|
114
|
+
return (req, res, next) => {
|
|
115
|
+
const start = Date.now();
|
|
116
|
+
const requestId = this.generateRequestId();
|
|
117
|
+
// Initialize metadata object on request
|
|
118
|
+
req.logMetadata = {};
|
|
119
|
+
// Detect SSE - check both request Accept header and response Content-Type
|
|
120
|
+
let isSSE = req.headers.accept === 'text/event-stream';
|
|
121
|
+
const chunks = [];
|
|
122
|
+
// Intercept setHeader to detect SSE by Content-Type
|
|
123
|
+
const originalSetHeader = res.setHeader.bind(res);
|
|
124
|
+
res.setHeader = ((name, value) => {
|
|
125
|
+
if (name.toLowerCase() === 'content-type' &&
|
|
126
|
+
typeof value === 'string' &&
|
|
127
|
+
value.includes('text/event-stream')) {
|
|
128
|
+
isSSE = true;
|
|
129
|
+
}
|
|
130
|
+
return originalSetHeader(name, value);
|
|
131
|
+
});
|
|
132
|
+
// Capture request info
|
|
133
|
+
const requestInfo = {
|
|
134
|
+
body: req.body,
|
|
135
|
+
query: req.query,
|
|
136
|
+
};
|
|
137
|
+
if (this.includeHeaders) {
|
|
138
|
+
requestInfo.headers = req.headers;
|
|
139
|
+
}
|
|
140
|
+
// Intercept write/end for streaming detection
|
|
141
|
+
const originalWrite = res.write.bind(res);
|
|
142
|
+
const originalEnd = res.end.bind(res);
|
|
143
|
+
const originalJson = res.json.bind(res);
|
|
144
|
+
const originalSend = res.send.bind(res);
|
|
145
|
+
let responseBody;
|
|
146
|
+
let logged = false;
|
|
147
|
+
res.write = ((chunk, encodingOrCallback, callback) => {
|
|
148
|
+
// If write is called, treat as streaming
|
|
149
|
+
if (chunk && isSSE) {
|
|
150
|
+
const chunkStr = chunk.toString();
|
|
151
|
+
const parsed = this.parseSSEChunk(chunkStr);
|
|
152
|
+
if (parsed) {
|
|
153
|
+
chunks.push(parsed);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return originalWrite(chunk, encodingOrCallback, callback);
|
|
157
|
+
});
|
|
158
|
+
res.end = ((chunk, encodingOrCallback, callback) => {
|
|
159
|
+
if (logged)
|
|
160
|
+
return originalEnd(chunk, encodingOrCallback, callback);
|
|
161
|
+
logged = true;
|
|
162
|
+
if (isSSE) {
|
|
163
|
+
// SSE streaming path
|
|
164
|
+
if (chunk) {
|
|
165
|
+
const chunkStr = chunk.toString();
|
|
166
|
+
const parsed = this.parseSSEChunk(chunkStr);
|
|
167
|
+
if (parsed) {
|
|
168
|
+
chunks.push(parsed);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const entry = {
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
requestId,
|
|
174
|
+
type: 'http',
|
|
175
|
+
method: req.method,
|
|
176
|
+
path: req.originalUrl || req.url,
|
|
177
|
+
statusCode: res.statusCode,
|
|
178
|
+
duration: Date.now() - start,
|
|
179
|
+
request: requestInfo,
|
|
180
|
+
response: {
|
|
181
|
+
streaming: true,
|
|
182
|
+
chunks,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
if (req.logMetadata && Object.keys(req.logMetadata).length > 0) {
|
|
186
|
+
entry.metadata = req.logMetadata;
|
|
187
|
+
}
|
|
188
|
+
this.write(entry);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Regular response path
|
|
192
|
+
const entry = {
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
requestId,
|
|
195
|
+
type: 'http',
|
|
196
|
+
method: req.method,
|
|
197
|
+
path: req.originalUrl || req.url,
|
|
198
|
+
statusCode: res.statusCode,
|
|
199
|
+
duration: Date.now() - start,
|
|
200
|
+
request: requestInfo,
|
|
201
|
+
response: {
|
|
202
|
+
body: responseBody,
|
|
203
|
+
streaming: false,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
if (req.logMetadata && Object.keys(req.logMetadata).length > 0) {
|
|
207
|
+
entry.metadata = req.logMetadata;
|
|
208
|
+
}
|
|
209
|
+
this.write(entry);
|
|
210
|
+
}
|
|
211
|
+
return originalEnd(chunk, encodingOrCallback, callback);
|
|
212
|
+
});
|
|
213
|
+
const logResponse = () => {
|
|
214
|
+
if (logged)
|
|
215
|
+
return;
|
|
216
|
+
logged = true;
|
|
217
|
+
const entry = {
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
requestId,
|
|
220
|
+
type: 'http',
|
|
221
|
+
method: req.method,
|
|
222
|
+
path: req.originalUrl || req.url,
|
|
223
|
+
statusCode: res.statusCode,
|
|
224
|
+
duration: Date.now() - start,
|
|
225
|
+
request: requestInfo,
|
|
226
|
+
response: {
|
|
227
|
+
body: responseBody,
|
|
228
|
+
streaming: false,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
if (req.logMetadata && Object.keys(req.logMetadata).length > 0) {
|
|
232
|
+
entry.metadata = req.logMetadata;
|
|
233
|
+
}
|
|
234
|
+
this.write(entry);
|
|
235
|
+
};
|
|
236
|
+
res.json = (body) => {
|
|
237
|
+
responseBody = body;
|
|
238
|
+
logResponse();
|
|
239
|
+
return originalJson(body);
|
|
240
|
+
};
|
|
241
|
+
res.send = (body) => {
|
|
242
|
+
if (!logged) {
|
|
243
|
+
try {
|
|
244
|
+
responseBody = typeof body === 'string' ? JSON.parse(body) : body;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
responseBody = body;
|
|
248
|
+
}
|
|
249
|
+
logResponse();
|
|
250
|
+
}
|
|
251
|
+
return originalSend(body);
|
|
252
|
+
};
|
|
253
|
+
next();
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
parseSSEChunk(raw) {
|
|
257
|
+
const lines = raw.split('\n');
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
if (line.startsWith('data: ')) {
|
|
260
|
+
const data = line.slice(6).trim();
|
|
261
|
+
if (data === '[DONE]')
|
|
262
|
+
return { done: true };
|
|
263
|
+
try {
|
|
264
|
+
return JSON.parse(data);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return { raw: data };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
// ===========================================================================
|
|
274
|
+
// tRPC Middleware
|
|
275
|
+
// ===========================================================================
|
|
276
|
+
trpcMiddleware() {
|
|
277
|
+
const logger = this;
|
|
278
|
+
return async function loggerMiddleware(opts) {
|
|
279
|
+
const start = Date.now();
|
|
280
|
+
const requestId = logger.generateRequestId();
|
|
281
|
+
// Initialize metadata on context if not present
|
|
282
|
+
if (!opts.ctx.logMetadata) {
|
|
283
|
+
opts.ctx.logMetadata = {};
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const result = await opts.next();
|
|
287
|
+
const entry = {
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
requestId,
|
|
290
|
+
type: 'trpc',
|
|
291
|
+
method: opts.type.toUpperCase(),
|
|
292
|
+
path: opts.path,
|
|
293
|
+
statusCode: result.ok ? 200 : 500,
|
|
294
|
+
duration: Date.now() - start,
|
|
295
|
+
request: {
|
|
296
|
+
body: opts.input,
|
|
297
|
+
},
|
|
298
|
+
response: {
|
|
299
|
+
body: result.data,
|
|
300
|
+
streaming: false,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
// Attach metadata if present
|
|
304
|
+
if (opts.ctx.logMetadata && Object.keys(opts.ctx.logMetadata).length > 0) {
|
|
305
|
+
entry.metadata = opts.ctx.logMetadata;
|
|
306
|
+
}
|
|
307
|
+
if (result.error) {
|
|
308
|
+
entry.error = {
|
|
309
|
+
message: result.error.message,
|
|
310
|
+
stack: result.error.stack,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
logger.write(entry);
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
const err = error;
|
|
318
|
+
const entry = {
|
|
319
|
+
timestamp: new Date().toISOString(),
|
|
320
|
+
requestId,
|
|
321
|
+
type: 'trpc',
|
|
322
|
+
method: opts.type.toUpperCase(),
|
|
323
|
+
path: opts.path,
|
|
324
|
+
statusCode: 500,
|
|
325
|
+
duration: Date.now() - start,
|
|
326
|
+
request: {
|
|
327
|
+
body: opts.input,
|
|
328
|
+
},
|
|
329
|
+
error: {
|
|
330
|
+
message: err.message,
|
|
331
|
+
stack: err.stack,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
// Attach metadata if present
|
|
335
|
+
if (opts.ctx.logMetadata && Object.keys(opts.ctx.logMetadata).length > 0) {
|
|
336
|
+
entry.metadata = opts.ctx.logMetadata;
|
|
337
|
+
}
|
|
338
|
+
logger.write(entry);
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// Factory Function (Main Export)
|
|
346
|
+
// ============================================================================
|
|
347
|
+
function createLogger(filePath, options) {
|
|
348
|
+
const logger = new UnifiedLogger(filePath, options);
|
|
349
|
+
return {
|
|
350
|
+
/** Express middleware - use with app.use() */
|
|
351
|
+
express: () => logger.expressMiddleware(),
|
|
352
|
+
/** tRPC middleware - use with t.procedure.use() */
|
|
353
|
+
trpc: () => logger.trpcMiddleware(),
|
|
354
|
+
/** Direct write access for custom logging */
|
|
355
|
+
write: (entry) => logger.write({
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
requestId: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`,
|
|
358
|
+
type: 'http',
|
|
359
|
+
method: 'CUSTOM',
|
|
360
|
+
path: '/',
|
|
361
|
+
duration: 0,
|
|
362
|
+
...entry,
|
|
363
|
+
}),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
// ============================================================================
|
|
367
|
+
// Helper to attach metadata (for cleaner API)
|
|
368
|
+
// ============================================================================
|
|
369
|
+
/**
|
|
370
|
+
* Attach metadata to the current request log entry (Express)
|
|
371
|
+
*/
|
|
372
|
+
function attachMetadata(req, metadata) {
|
|
373
|
+
if (!req.logMetadata) {
|
|
374
|
+
req.logMetadata = {};
|
|
375
|
+
}
|
|
376
|
+
Object.assign(req.logMetadata, metadata);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Attach metadata to the current request log entry (tRPC)
|
|
380
|
+
*/
|
|
381
|
+
function attachTrpcMetadata(ctx, metadata) {
|
|
382
|
+
if (!ctx.logMetadata) {
|
|
383
|
+
ctx.logMetadata = {};
|
|
384
|
+
}
|
|
385
|
+
Object.assign(ctx.logMetadata, metadata);
|
|
386
|
+
}
|
|
387
|
+
// Default export for simpler imports
|
|
388
|
+
exports.default = createLogger;
|
|
389
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9nZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2xvZ2dlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQWlaQSxvQ0FzQkM7QUFTRCx3Q0FLQztBQUtELGdEQVFDO0FBbGNELHVDQUF5QjtBQUN6QiwyQ0FBNkI7QUErQzdCLCtFQUErRTtBQUMvRSxvQkFBb0I7QUFDcEIsK0VBQStFO0FBRS9FLE1BQU0sYUFBYTtJQU1qQixZQUFZLFFBQWdCLEVBQUUsVUFBeUIsRUFBRTtRQUN2RCxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDdkMsSUFBSSxDQUFDLFlBQVksR0FBRyxPQUFPLENBQUMsWUFBWSxJQUFJLEVBQUUsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsZUFBZTtRQUM3RSxJQUFJLENBQUMsY0FBYyxHQUFHLE9BQU8sQ0FBQyxjQUFjLElBQUksS0FBSyxDQUFDO1FBQ3RELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFFaEcsMEJBQTBCO1FBQzFCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3hDLElBQUksQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDeEIsRUFBRSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN6QyxDQUFDO0lBQ0gsQ0FBQztJQUVPLGlCQUFpQjtRQUN2QixPQUFPLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFF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QUFDO0lBQ3ZCLENBQUM7SUFDRCxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0Isa0JBQWtCLENBQ2hDLEdBQWEsRUFDYixRQUFpQztJQUVqQyxJQUFJLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ3JCLEdBQUcsQ0FBQyxXQUFXLEdBQUcsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFDRCxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVELHFDQUFxQztBQUNyQyxrQkFBZSxZQUFZLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBmcyBmcm9tICdmcyc7XG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5cbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIFR5cGVzXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbmludGVyZmFjZSBMb2dFbnRyeSB7XG4gIHRpbWVzdGFtcDogc3RyaW5nO1xuICByZXF1ZXN0SWQ6IHN0cmluZztcbiAgdHlwZTogJ2h0dHAnIHwgJ3RycGMnO1xuICBtZXRob2Q6IHN0cmluZztcbiAgcGF0aDogc3RyaW5nO1xuICBzdGF0dXNDb2RlPzogbnVtYmVyO1xuICBkdXJhdGlvbjogbnVtYmVyO1xuICByZXF1ZXN0Pzoge1xuICAgIGJvZHk/OiB1bmtub3duO1xuICAgIHF1ZXJ5PzogdW5rbm93bjtcbiAgICBoZWFkZXJzPzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcbiAgfTtcbiAgcmVzcG9uc2U/OiB7XG4gICAgYm9keT86IHVua25vd247XG4gICAgc3RyZWFtaW5nPzogYm9vbGVhbjtcbiAgICBjaHVua3M/OiB1bmtub3duW107XG4gIH07XG4gIGVycm9yPzoge1xuICAgIG1lc3NhZ2U6IHN0cmluZztcbiAgICBzdGFjaz86IHN0cmluZztcbiAgfTtcbiAgbWV0YWRhdGE/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbn1cblxuaW50ZXJmYWNlIExvZ2dlck9wdGlvbnMge1xuICBtYXhTaXplQnl0ZXM/OiBudW1iZXI7ICAgICAgLy8gRGVmYXVsdDogMTBNQlxuICBpbmNsdWRlSGVhZGVycz86IGJvb2xlYW47ICAgLy8gRGVmYXVsdDogZmFsc2VcbiAgcmVkYWN0Pzogc3RyaW5nW107ICAgICAgICAgIC8vIEZpZWxkcyB0byByZWRhY3QgZnJvbSBsb2dzXG59XG5cbi8vIEV4dGVuZCBFeHByZXNzIFJlcXVlc3QgdG8gaW5jbHVkZSBtZXRhZGF0YVxuZGVjbGFyZSBnbG9iYWwge1xuICBuYW1lc3BhY2UgRXhwcmVzcyB7XG4gICAgaW50ZXJmYWNlIFJlcXVlc3Qge1xuICAgICAgbG9nTWV0YWRhdGE/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgICB9XG4gIH1cbn1cblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQ29yZSBMb2dnZXIgQ2xhc3Ncbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuY2xhc3MgVW5pZmllZExvZ2dlciB7XG4gIHByaXZhdGUgZmlsZVBhdGg6IHN0cmluZztcbiAgcHJpdmF0ZSBtYXhTaXplQnl0ZXM6IG51bWJlcjtcbiAgcHJpdmF0ZSBpbmNsdWRlSGVhZGVyczogYm9vbGVhbjtcbiAgcHJpdmF0ZSByZWRhY3RGaWVsZHM6IFNldDxzdHJpbmc+O1xuXG4gIGNvbnN0cnVjdG9yKGZpbGVQYXRoOiBzdHJpbmcsIG9wdGlvbnM6IExvZ2dlck9wdGlvbnMgPSB7fSkge1xuICAgIHRoaXMuZmlsZVBhdGggPSBwYXRoLnJlc29sdmUoZmlsZVBhdGgpO1xuICAgIHRoaXMubWF4U2l6ZUJ5dGVzID0gb3B0aW9ucy5tYXhTaXplQnl0ZXMgPz8gMTAgKiAxMDI0ICogMTAyNDsgLy8gMTBNQiBkZWZhdWx0XG4gICAgdGhpcy5pbmNsdWRlSGVhZGVycyA9IG9wdGlvbnMuaW5jbHVkZUhlYWRlcnMgPz8gZmFsc2U7XG4gICAgdGhpcy5yZWRhY3RGaWVsZHMgPSBuZXcgU2V0KG9wdGlvbnMucmVkYWN0ID8/IFsncGFzc3dvcmQnLCAndG9rZW4nLCAnYXV0aG9yaXphdGlvbicsICdjb29raWUnXSk7XG5cbiAgICAvLyBFbnN1cmUgZGlyZWN0b3J5IGV4aXN0c1xuICAgIGNvbnN0IGRpciA9IHBhdGguZGlybmFtZSh0aGlzLmZpbGVQYXRoKTtcbiAgICBpZiAoIWZzLmV4aXN0c1N5bmMoZGlyKSkge1xuICAgICAgZnMubWtkaXJTeW5jKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBnZW5lcmF0ZVJlcXVlc3RJZCgpOiBzdHJpbmcge1xuICAgIHJldHVybiBgJHtEYXRlLm5vdygpLnRvU3RyaW5nKDM2KX0tJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zbGljZSgyLCA5KX1gO1xuICB9XG5cbiAgcHJpdmF0ZSByZWRhY3Qob2JqOiB1bmtub3duKTogdW5rbm93biB7XG4gICAgaWYgKG9iaiA9PT0gbnVsbCB8fCBvYmogPT09IHVuZGVmaW5lZCkgcmV0dXJuIG9iajtcbiAgICBpZiAodHlwZW9mIG9iaiAhPT0gJ29iamVjdCcpIHJldHVybiBvYmo7XG5cbiAgICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgICByZXR1cm4gb2JqLm1hcCgoaXRlbSkgPT4gdGhpcy5yZWRhY3QoaXRlbSkpO1xuICAgIH1cblxuICAgIGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcbiAgICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhvYmogYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pKSB7XG4gICAgICBpZiAodGhpcy5yZWRhY3RGaWVsZHMuaGFzKGtleS50b0xvd2VyQ2FzZSgpKSkge1xuICAgICAgICByZXN1bHRba2V5XSA9ICdbUkVEQUNURURdJztcbiAgICAgIH0gZWxzZSBpZiAodHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jykge1xuICAgICAgICByZXN1bHRba2V5XSA9IHRoaXMucmVkYWN0KHZhbHVlKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlc3VsdFtrZXldID0gdmFsdWU7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cblxuICBwcml2YXRlIGNoZWNrQW5kUm90YXRlKCk6IHZvaWQge1xuICAgIHRyeSB7XG4gICAgICBpZiAoIWZzLmV4aXN0c1N5bmModGhpcy5maWxlUGF0aCkpIHJldHVybjtcblxuICAgICAgY29uc3Qgc3RhdHMgPSBmcy5zdGF0U3luYyh0aGlzLmZpbGVQYXRoKTtcbiAgICAgIGlmIChzdGF0cy5zaXplID4gdGhpcy5tYXhTaXplQnl0ZXMpIHtcbiAgICAgICAgLy8gUmVhZCBmaWxlLCBrZWVwIGxhc3QgMjUlIG9mIGxpbmVzXG4gICAgICAgIGNvbnN0IGNvbnRlbnQgPSBmcy5yZWFkRmlsZVN5bmModGhpcy5maWxlUGF0aCwgJ3V0Zi04Jyk7XG4gICAgICAgIGNvbnN0IGxpbmVzID0gY29udGVudC50cmltKCkuc3BsaXQoJ1xcbicpO1xuICAgICAgICBjb25zdCBrZWVwQ291bnQgPSBNYXRoLmZsb29yKGxpbmVzLmxlbmd0aCAqIDAuMjUpO1xuICAgICAgICBjb25zdCBuZXdDb250ZW50ID0gbGluZXMuc2xpY2UoLWtlZXBDb3VudCkuam9pbignXFxuJykgKyAnXFxuJztcbiAgICAgICAgZnMud3JpdGVGaWxlU3luYyh0aGlzLmZpbGVQYXRoLCBuZXdDb250ZW50KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ0xvZ2dlciByb3RhdGlvbiBlcnJvcjonLCBlcnIpO1xuICAgIH1cbiAgfVxuXG4gIHdyaXRlKGVudHJ5OiBMb2dFbnRyeSk6IHZvaWQge1xuICAgIHRyeSB7XG4gICAgICB0aGlzLmNoZWNrQW5kUm90YXRlKCk7XG4gICAgICBjb25zdCByZWRhY3RlZEVudHJ5ID0gdGhpcy5yZWRhY3QoZW50cnkpIGFzIExvZ0VudHJ5O1xuICAgICAgY29uc3QgbGluZSA9IEpTT04uc3RyaW5naWZ5KHJlZGFjdGVkRW50cnkpICsgJ1xcbic7XG4gICAgICBmcy5hcHBlbmRGaWxlU3luYyh0aGlzLmZpbGVQYXRoLCBsaW5lKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ0xvZ2dlciB3cml0ZSBlcnJvcjonLCBlcnIpO1xuICAgIH1cbiAgfVxuXG4gIC8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAvLyBFeHByZXNzIE1pZGRsZXdhcmVcbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbiAgZXhwcmVzc01pZGRsZXdhcmUoKSB7XG4gICAgcmV0dXJuIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbikgPT4ge1xuICAgICAgY29uc3Qgc3RhcnQgPSBEYXRlLm5vdygpO1xuICAgICAgY29uc3QgcmVxdWVzdElkID0gdGhpcy5nZW5lcmF0ZVJlcXVlc3RJZCgpO1xuXG4gICAgICAvLyBJbml0aWFsaXplIG1ldGFkYXRhIG9iamVjdCBvbiByZXF1ZXN0XG4gICAgICByZXEubG9nTWV0YWRhdGEgPSB7fTtcblxuICAgICAgLy8gRGV0ZWN0IFNTRSAtIGNoZWNrIGJvdGggcmVxdWVzdCBBY2NlcHQgaGVhZGVyIGFuZCByZXNwb25zZSBDb250ZW50LVR5cGVcbiAgICAgIGxldCBpc1NTRSA9IHJlcS5oZWFkZXJzLmFjY2VwdCA9PT0gJ3RleHQvZXZlbnQtc3RyZWFtJztcbiAgICAgIGNvbnN0IGNodW5rczogdW5rbm93bltdID0gW107XG4gICAgICBcbiAgICAgIC8vIEludGVyY2VwdCBzZXRIZWFkZXIgdG8gZGV0ZWN0IFNTRSBieSBDb250ZW50LVR5cGVcbiAgICAgIGNvbnN0IG9yaWdpbmFsU2V0SGVhZGVyID0gcmVzLnNldEhlYWRlci5iaW5kKHJlcyk7XG4gICAgICByZXMuc2V0SGVhZGVyID0gKChuYW1lOiBzdHJpbmcsIHZhbHVlOiBzdHJpbmcgfCBudW1iZXIgfCByZWFkb25seSBzdHJpbmdbXSk6IFJlc3BvbnNlID0+IHtcbiAgICAgICAgaWYgKG5hbWUudG9Mb3dlckNhc2UoKSA9PT0gJ2NvbnRlbnQtdHlwZScgJiYgXG4gICAgICAgICAgICB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnICYmIFxuICAgICAgICAgICAgdmFsdWUuaW5jbHVkZXMoJ3RleHQvZXZlbnQtc3RyZWFtJykpIHtcbiAgICAgICAgICBpc1NTRSA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG9yaWdpbmFsU2V0SGVhZGVyKG5hbWUsIHZhbHVlKTtcbiAgICAgIH0pIGFzIHR5cGVvZiByZXMuc2V0SGVhZGVyO1xuXG4gICAgICAvLyBDYXB0dXJlIHJlcXVlc3QgaW5mb1xuICAgICAgY29uc3QgcmVxdWVzdEluZm86IExvZ0VudHJ5WydyZXF1ZXN0J10gPSB7XG4gICAgICAgIGJvZHk6IHJlcS5ib2R5LFxuICAgICAgICBxdWVyeTogcmVxLnF1ZXJ5LFxuICAgICAgfTtcblxuICAgICAgaWYgKHRoaXMuaW5jbHVkZUhlYWRlcnMpIHtcbiAgICAgICAgcmVxdWVzdEluZm8uaGVhZGVycyA9IHJlcS5oZWFkZXJzIGFzIFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG4gICAgICB9XG5cbiAgICAgIC8vIEludGVyY2VwdCB3cml0ZS9lbmQgZm9yIHN0cmVhbWluZyBkZXRlY3Rpb25cbiAgICAgIGNvbnN0IG9yaWdpbmFsV3JpdGUgPSByZXMud3JpdGUuYmluZChyZXMpO1xuICAgICAgY29uc3Qgb3JpZ2luYWxFbmQgPSByZXMuZW5kLmJpbmQocmVzKTtcbiAgICAgIGNvbnN0IG9yaWdpbmFsSnNvbiA9IHJlcy5qc29uLmJpbmQocmVzKTtcbiAgICAgIGNvbnN0IG9yaWdpbmFsU2VuZCA9IHJlcy5zZW5kLmJpbmQocmVzKTtcbiAgICAgIGxldCByZXNwb25zZUJvZHk6IHVua25vd247XG4gICAgICBsZXQgbG9nZ2VkID0gZmFsc2U7XG5cbiAgICAgIHJlcy53cml0ZSA9ICgoY2h1bms6IGFueSwgZW5jb2RpbmdPckNhbGxiYWNrPzogYW55LCBjYWxsYmFjaz86IGFueSk6IGJvb2xlYW4gPT4ge1xuICAgICAgICAvLyBJZiB3cml0ZSBpcyBjYWxsZWQsIHRyZWF0IGFzIHN0cmVhbWluZ1xuICAgICAgICBpZiAoY2h1bmsgJiYgaXNTU0UpIHtcbiAgICAgICAgICBjb25zdCBjaHVua1N0ciA9IGNodW5rLnRvU3RyaW5nKCk7XG4gICAgICAgICAgY29uc3QgcGFyc2VkID0gdGhpcy5wYXJzZVNTRUNodW5rKGNodW5rU3RyKTtcbiAgICAgICAgICBpZiAocGFyc2VkKSB7XG4gICAgICAgICAgICBjaHVua3MucHVzaChwYXJzZWQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3JpZ2luYWxXcml0ZShjaHVuaywgZW5jb2RpbmdPckNhbGxiYWNrLCBjYWxsYmFjayk7XG4gICAgICB9KSBhcyB0eXBlb2YgcmVzLndyaXRlO1xuXG4gICAgICByZXMuZW5kID0gKChjaHVuaz86IGFueSwgZW5jb2RpbmdPckNhbGxiYWNrPzogYW55LCBjYWxsYmFjaz86IGFueSk6IFJlc3BvbnNlID0+IHtcbiAgICAgICAgaWYgKGxvZ2dlZCkgcmV0dXJuIG9yaWdpbmFsRW5kKGNodW5rLCBlbmNvZGluZ09yQ2FsbGJhY2ssIGNhbGxiYWNrKTtcbiAgICAgICAgbG9nZ2VkID0gdHJ1ZTtcblxuICAgICAgICBpZiAoaXNTU0UpIHtcbiAgICAgICAgICAvLyBTU0Ugc3RyZWFtaW5nIHBhdGhcbiAgICAgICAgICBpZiAoY2h1bmspIHtcbiAgICAgICAgICAgIGNvbnN0IGNodW5rU3RyID0gY2h1bmsudG9TdHJpbmcoKTtcbiAgICAgICAgICAgIGNvbnN0IHBhcnNlZCA9IHRoaXMucGFyc2VTU0VDaHVuayhjaHVua1N0cik7XG4gICAgICAgICAgICBpZiAocGFyc2VkKSB7XG4gICAgICAgICAgICAgIGNodW5rcy5wdXNoKHBhcnNlZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgY29uc3QgZW50cnk6IExvZ0VudHJ5ID0ge1xuICAgICAgICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgICAgICB0eXBlOiAnaHR0cCcsXG4gICAgICAgICAgICBtZXRob2Q6IHJlcS5tZXRob2QsXG4gICAgICAgICAgICBwYXRoOiByZXEub3JpZ2luYWxVcmwgfHwgcmVxLnVybCxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IHJlcy5zdGF0dXNDb2RlLFxuICAgICAgICAgICAgZHVyYXRpb246IERhdGUubm93KCkgLSBzdGFydCxcbiAgICAgICAgICAgIHJlcXVlc3Q6IHJlcXVlc3RJbmZvLFxuICAgICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgICAgc3RyZWFtaW5nOiB0cnVlLFxuICAgICAgICAgICAgICBjaHVua3MsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH07XG5cbiAgICAgICAgICBpZiAocmVxLmxvZ01ldGFkYXRhICYmIE9iamVjdC5rZXlzKHJlcS5sb2dNZXRhZGF0YSkubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgZW50cnkubWV0YWRhdGEgPSByZXEubG9nTWV0YWRhdGE7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgdGhpcy53cml0ZShlbnRyeSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gUmVndWxhciByZXNwb25zZSBwYXRoXG4gICAgICAgICAgY29uc3QgZW50cnk6IExvZ0VudHJ5ID0ge1xuICAgICAgICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgICAgICB0eXBlOiAnaHR0cCcsXG4gICAgICAgICAgICBtZXRob2Q6IHJlcS5tZXRob2QsXG4gICAgICAgICAgICBwYXRoOiByZXEub3JpZ2luYWxVcmwgfHwgcmVxLnVybCxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IHJlcy5zdGF0dXNDb2RlLFxuICAgICAgICAgICAgZHVyYXRpb246IERhdGUubm93KCkgLSBzdGFydCxcbiAgICAgICAgICAgIHJlcXVlc3Q6IHJlcXVlc3RJbmZvLFxuICAgICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgICAgYm9keTogcmVzcG9uc2VCb2R5LFxuICAgICAgICAgICAgICBzdHJlYW1pbmc6IGZhbHNlLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgaWYgKHJlcS5sb2dNZXRhZGF0YSAmJiBPYmplY3Qua2V5cyhyZXEubG9nTWV0YWRhdGEpLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGVudHJ5Lm1ldGFkYXRhID0gcmVxLmxvZ01ldGFkYXRhO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHRoaXMud3JpdGUoZW50cnkpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG9yaWdpbmFsRW5kKGNodW5rLCBlbmNvZGluZ09yQ2FsbGJhY2ssIGNhbGxiYWNrKTtcbiAgICAgIH0pIGFzIHR5cGVvZiByZXMuZW5kO1xuXG4gICAgICBjb25zdCBsb2dSZXNwb25zZSA9ICgpID0+IHtcbiAgICAgICAgaWYgKGxvZ2dlZCkgcmV0dXJuO1xuICAgICAgICBsb2dnZWQgPSB0cnVlO1xuXG4gICAgICAgIGNvbnN0IGVudHJ5OiBMb2dFbnRyeSA9IHtcbiAgICAgICAgICB0aW1lc3RhbXA6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKSxcbiAgICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgICAgdHlwZTogJ2h0dHAnLFxuICAgICAgICAgIG1ldGhvZDogcmVxLm1ldGhvZCxcbiAgICAgICAgICBwYXRoOiByZXEub3JpZ2luYWxVcmwgfHwgcmVxLnVybCxcbiAgICAgICAgICBzdGF0dXNDb2RlOiByZXMuc3RhdHVzQ29kZSxcbiAgICAgICAgICBkdXJhdGlvbjogRGF0ZS5ub3coKSAtIHN0YXJ0LFxuICAgICAgICAgIHJlcXVlc3Q6IHJlcXVlc3RJbmZvLFxuICAgICAgICAgIHJlc3BvbnNlOiB7XG4gICAgICAgICAgICBib2R5OiByZXNwb25zZUJvZHksXG4gICAgICAgICAgICBzdHJlYW1pbmc6IGZhbHNlLFxuICAgICAgICAgIH0sXG4gICAgICAgIH07XG5cbiAgICAgICAgaWYgKHJlcS5sb2dNZXRhZGF0YSAmJiBPYmplY3Qua2V5cyhyZXEubG9nTWV0YWRhdGEpLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBlbnRyeS5tZXRhZGF0YSA9IHJlcS5sb2dNZXRhZGF0YTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMud3JpdGUoZW50cnkpO1xuICAgICAgfTtcblxuICAgICAgcmVzLmpzb24gPSAoYm9keTogYW55KSA9PiB7XG4gICAgICAgIHJlc3BvbnNlQm9keSA9IGJvZHk7XG4gICAgICAgIGxvZ1Jlc3BvbnNlKCk7XG4gICAgICAgIHJldHVybiBvcmlnaW5hbEpzb24oYm9keSk7XG4gICAgICB9O1xuXG4gICAgICByZXMuc2VuZCA9IChib2R5OiBhbnkpID0+IHtcbiAgICAgICAgaWYgKCFsb2dnZWQpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmVzcG9uc2VCb2R5ID0gdHlwZW9mIGJvZHkgPT09ICdzdHJpbmcnID8gSlNPTi5wYXJzZShib2R5KSA6IGJvZHk7XG4gICAgICAgICAgfSBjYXRjaCB7XG4gICAgICAgICAgICByZXNwb25zZUJvZHkgPSBib2R5O1xuICAgICAgICAgIH1cbiAgICAgICAgICBsb2dSZXNwb25zZSgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvcmlnaW5hbFNlbmQoYm9keSk7XG4gICAgICB9O1xuXG4gICAgICBuZXh0KCk7XG4gICAgfTtcbiAgfVxuXG4gIHByaXZhdGUgcGFyc2VTU0VDaHVuayhyYXc6IHN0cmluZyk6IHVua25vd24ge1xuICAgIGNvbnN0IGxpbmVzID0gcmF3LnNwbGl0KCdcXG4nKTtcbiAgICBmb3IgKGNvbnN0IGxpbmUgb2YgbGluZXMpIHtcbiAgICAgIGlmIChsaW5lLnN0YXJ0c1dpdGgoJ2RhdGE6ICcpKSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSBsaW5lLnNsaWNlKDYpLnRyaW0oKTtcbiAgICAgICAgaWYgKGRhdGEgPT09ICdbRE9ORV0nKSByZXR1cm4geyBkb25lOiB0cnVlIH07XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG4gICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgIHJldHVybiB7IHJhdzogZGF0YSB9O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gIC8vIHRSUEMgTWlkZGxld2FyZVxuICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cblxuICB0cnBjTWlkZGxld2FyZTxUQ29udGV4dCBleHRlbmRzIFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0gUmVjb3JkPHN0cmluZywgdW5rbm93bj4+KCkge1xuICAgIGNvbnN0IGxvZ2dlciA9IHRoaXM7XG5cbiAgICByZXR1cm4gYXN5bmMgZnVuY3Rpb24gbG9nZ2VyTWlkZGxld2FyZShvcHRzOiB7XG4gICAgICBwYXRoOiBzdHJpbmc7XG4gICAgICB0eXBlOiAncXVlcnknIHwgJ211dGF0aW9uJyB8ICdzdWJzY3JpcHRpb24nO1xuICAgICAgaW5wdXQ6IHVua25vd247XG4gICAgICBjdHg6IFRDb250ZXh0ICYgeyBsb2dNZXRhZGF0YT86IFJlY29yZDxzdHJpbmcsIHVua25vd24+IH07XG4gICAgICBuZXh0OiAoKSA9PiBQcm9taXNlPHsgb2s6IGJvb2xlYW47IGRhdGE/OiB1bmtub3duOyBlcnJvcj86IEVycm9yIH0+O1xuICAgIH0pIHtcbiAgICAgIGNvbnN0IHN0YXJ0ID0gRGF0ZS5ub3coKTtcbiAgICAgIGNvbnN0IHJlcXVlc3RJZCA9IGxvZ2dlci5nZW5lcmF0ZVJlcXVlc3RJZCgpO1xuXG4gICAgICAvLyBJbml0aWFsaXplIG1ldGFkYXRhIG9uIGNvbnRleHQgaWYgbm90IHByZXNlbnRcbiAgICAgIGlmICghb3B0cy5jdHgubG9nTWV0YWRhdGEpIHtcbiAgICAgICAgb3B0cy5jdHgubG9nTWV0YWRhdGEgPSB7fTtcbiAgICAgIH1cblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgb3B0cy5uZXh0KCk7XG5cbiAgICAgICAgY29uc3QgZW50cnk6IExvZ0VudHJ5ID0ge1xuICAgICAgICAgIHRpbWVzdGFtcDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgICAgIHJlcXVlc3RJZCxcbiAgICAgICAgICB0eXBlOiAndHJwYycsXG4gICAgICAgICAgbWV0aG9kOiBvcHRzLnR5cGUudG9VcHBlckNhc2UoKSxcbiAgICAgICAgICBwYXRoOiBvcHRzLnBhdGgsXG4gICAgICAgICAgc3RhdHVzQ29kZTogcmVzdWx0Lm9rID8gMjAwIDogNTAwLFxuICAgICAgICAgIGR1cmF0aW9uOiBEYXRlLm5vdygpIC0gc3RhcnQsXG4gICAgICAgICAgcmVxdWVzdDoge1xuICAgICAgICAgICAgYm9keTogb3B0cy5pbnB1dCxcbiAgICAgICAgICB9LFxuICAgICAgICAgIHJlc3BvbnNlOiB7XG4gICAgICAgICAgICBib2R5OiByZXN1bHQuZGF0YSxcbiAgICAgICAgICAgIHN0cmVhbWluZzogZmFsc2UsXG4gICAgICAgICAgfSxcbiAgICAgICAgfTtcblxuICAgICAgICAvLyBBdHRhY2ggbWV0YWRhdGEgaWYgcHJlc2VudFxuICAgICAgICBpZiAob3B0cy5jdHgubG9nTWV0YWRhdGEgJiYgT2JqZWN0LmtleXMob3B0cy5jdHgubG9nTWV0YWRhdGEpLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBlbnRyeS5tZXRhZGF0YSA9IG9wdHMuY3R4LmxvZ01ldGFkYXRhO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHJlc3VsdC5lcnJvcikge1xuICAgICAgICAgIGVudHJ5LmVycm9yID0ge1xuICAgICAgICAgICAgbWVzc2FnZTogcmVzdWx0LmVycm9yLm1lc3NhZ2UsXG4gICAgICAgICAgICBzdGFjazogcmVzdWx0LmVycm9yLnN0YWNrLFxuICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICBsb2dnZXIud3JpdGUoZW50cnkpO1xuXG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBjb25zdCBlcnIgPSBlcnJvciBhcyBFcnJvcjtcblxuICAgICAgICBjb25zdCBlbnRyeTogTG9nRW50cnkgPSB7XG4gICAgICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICAgICAgcmVxdWVzdElkLFxuICAgICAgICAgIHR5cGU6ICd0cnBjJyxcbiAgICAgICAgICBtZXRob2Q6IG9wdHMudHlwZS50b1VwcGVyQ2FzZSgpLFxuICAgICAgICAgIHBhdGg6IG9wdHMucGF0aCxcbiAgICAgICAgICBzdGF0dXNDb2RlOiA1MDAsXG4gICAgICAgICAgZHVyYXRpb246IERhdGUubm93KCkgLSBzdGFydCxcbiAgICAgICAgICByZXF1ZXN0OiB7XG4gICAgICAgICAgICBib2R5OiBvcHRzLmlucHV0LFxuICAgICAgICAgIH0sXG4gICAgICAgICAgZXJyb3I6IHtcbiAgICAgICAgICAgIG1lc3NhZ2U6IGVyci5tZXNzYWdlLFxuICAgICAgICAgICAgc3RhY2s6IGVyci5zdGFjayxcbiAgICAgICAgICB9LFxuICAgICAgICB9O1xuXG4gICAgICAgIC8vIEF0dGFjaCBtZXRhZGF0YSBpZiBwcmVzZW50XG4gICAgICAgIGlmIChvcHRzLmN0eC5sb2dNZXRhZGF0YSAmJiBPYmplY3Qua2V5cyhvcHRzLmN0eC5sb2dNZXRhZGF0YSkubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGVudHJ5Lm1ldGFkYXRhID0gb3B0cy5jdHgubG9nTWV0YWRhdGE7XG4gICAgICAgIH1cblxuICAgICAgICBsb2dnZXIud3JpdGUoZW50cnkpO1xuXG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfVxuICAgIH07XG4gIH1cbn1cblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gRmFjdG9yeSBGdW5jdGlvbiAoTWFpbiBFeHBvcnQpXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVMb2dnZXIoZmlsZVBhdGg6IHN0cmluZywgb3B0aW9ucz86IExvZ2dlck9wdGlvbnMpIHtcbiAgY29uc3QgbG9nZ2VyID0gbmV3IFVuaWZpZWRMb2dnZXIoZmlsZVBhdGgsIG9wdGlvbnMpO1xuXG4gIHJldHVybiB7XG4gICAgLyoqIEV4cHJlc3MgbWlkZGxld2FyZSAtIHVzZSB3aXRoIGFwcC51c2UoKSAqL1xuICAgIGV4cHJlc3M6ICgpID0+IGxvZ2dlci5leHByZXNzTWlkZGxld2FyZSgpLFxuXG4gICAgLyoqIHRSUEMgbWlkZGxld2FyZSAtIHVzZSB3aXRoIHQucHJvY2VkdXJlLnVzZSgpICovXG4gICAgdHJwYzogPFRDb250ZXh0IGV4dGVuZHMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4oKSA9PiBcbiAgICAgIGxvZ2dlci50cnBjTWlkZGxld2FyZTxUQ29udGV4dD4oKSxcblxuICAgIC8qKiBEaXJlY3Qgd3JpdGUgYWNjZXNzIGZvciBjdXN0b20gbG9nZ2luZyAqL1xuICAgIHdyaXRlOiAoZW50cnk6IFBhcnRpYWw8TG9nRW50cnk+KSA9PiBsb2dnZXIud3JpdGUoe1xuICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICByZXF1ZXN0SWQ6IGAke0RhdGUubm93KCkudG9TdHJpbmcoMzYpfS0ke01hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnNsaWNlKDIsIDkpfWAsXG4gICAgICB0eXBlOiAnaHR0cCcsXG4gICAgICBtZXRob2Q6ICdDVVNUT00nLFxuICAgICAgcGF0aDogJy8nLFxuICAgICAgZHVyYXRpb246IDAsXG4gICAgICAuLi5lbnRyeSxcbiAgICB9IGFzIExvZ0VudHJ5KSxcbiAgfTtcbn1cblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gSGVscGVyIHRvIGF0dGFjaCBtZXRhZGF0YSAoZm9yIGNsZWFuZXIgQVBJKVxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG4vKipcbiAqIEF0dGFjaCBtZXRhZGF0YSB0byB0aGUgY3VycmVudCByZXF1ZXN0IGxvZyBlbnRyeSAoRXhwcmVzcylcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF0dGFjaE1ldGFkYXRhKHJlcTogUmVxdWVzdCwgbWV0YWRhdGE6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogdm9pZCB7XG4gIGlmICghcmVxLmxvZ01ldGFkYXRhKSB7XG4gICAgcmVxLmxvZ01ldGFkYXRhID0ge307XG4gIH1cbiAgT2JqZWN0LmFzc2lnbihyZXEubG9nTWV0YWRhdGEsIG1ldGFkYXRhKTtcbn1cblxuLyoqXG4gKiBBdHRhY2ggbWV0YWRhdGEgdG8gdGhlIGN1cnJlbnQgcmVxdWVzdCBsb2cgZW50cnkgKHRSUEMpXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhdHRhY2hUcnBjTWV0YWRhdGE8VENvbnRleHQgZXh0ZW5kcyB7IGxvZ01ldGFkYXRhPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfT4oXG4gIGN0eDogVENvbnRleHQsXG4gIG1ldGFkYXRhOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuKTogdm9pZCB7XG4gIGlmICghY3R4LmxvZ01ldGFkYXRhKSB7XG4gICAgY3R4LmxvZ01ldGFkYXRhID0ge307XG4gIH1cbiAgT2JqZWN0LmFzc2lnbihjdHgubG9nTWV0YWRhdGEsIG1ldGFkYXRhKTtcbn1cblxuLy8gRGVmYXVsdCBleHBvcnQgZm9yIHNpbXBsZXIgaW1wb3J0c1xuZXhwb3J0IGRlZmF1bHQgY3JlYXRlTG9nZ2VyO1xuIl19
|
package/example/usage.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { initTRPC } from '@trpc/server';
|
|
3
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
4
|
+
import { createLogger, attachMetadata, attachTrpcMetadata } from '../src/logger';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Create the logger (single instance for both Express and tRPC)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
const logger = createLogger('./logs/app.log', {
|
|
11
|
+
maxSizeBytes: 10 * 1024 * 1024, // 10MB, then truncate to 25%
|
|
12
|
+
redact: ['password', 'token', 'secret', 'authorization'],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Express Setup
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
app.use(express.json());
|
|
21
|
+
|
|
22
|
+
// Add logging middleware - that's it!
|
|
23
|
+
app.use(logger.express());
|
|
24
|
+
|
|
25
|
+
// Regular endpoint
|
|
26
|
+
app.get('/api/health', (req, res) => {
|
|
27
|
+
res.json({ status: 'ok', timestamp: Date.now() });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Endpoint with metadata
|
|
31
|
+
app.get('/api/users/:id', (req, res) => {
|
|
32
|
+
// Attach arbitrary metadata to the log entry
|
|
33
|
+
attachMetadata(req, {
|
|
34
|
+
userId: req.params.id,
|
|
35
|
+
source: 'user-service',
|
|
36
|
+
cacheHit: false,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
res.json({ id: req.params.id, name: 'John Doe' });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// POST endpoint with metadata
|
|
43
|
+
app.post('/api/orders', (req, res) => {
|
|
44
|
+
const orderId = Math.random().toString(36).slice(2);
|
|
45
|
+
|
|
46
|
+
// Attach order-specific metadata
|
|
47
|
+
attachMetadata(req, {
|
|
48
|
+
orderId,
|
|
49
|
+
itemCount: req.body.items?.length ?? 0,
|
|
50
|
+
totalAmount: req.body.total,
|
|
51
|
+
region: 'us-east-1',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
res.json({ orderId, status: 'created' });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// SSE streaming endpoint with metadata
|
|
58
|
+
app.get('/api/stream', (req, res) => {
|
|
59
|
+
// Attach metadata before streaming starts
|
|
60
|
+
attachMetadata(req, {
|
|
61
|
+
streamType: 'events',
|
|
62
|
+
clientId: req.query.clientId || 'anonymous',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
66
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
67
|
+
res.setHeader('Connection', 'keep-alive');
|
|
68
|
+
|
|
69
|
+
let count = 0;
|
|
70
|
+
const interval = setInterval(() => {
|
|
71
|
+
count++;
|
|
72
|
+
res.write(`data: ${JSON.stringify({ count, message: `Event ${count}` })}\n\n`);
|
|
73
|
+
|
|
74
|
+
if (count >= 5) {
|
|
75
|
+
res.write('data: [DONE]\n\n');
|
|
76
|
+
clearInterval(interval);
|
|
77
|
+
res.end();
|
|
78
|
+
}
|
|
79
|
+
}, 100);
|
|
80
|
+
|
|
81
|
+
req.on('close', () => {
|
|
82
|
+
clearInterval(interval);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// tRPC Setup
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
// Define context type with logMetadata
|
|
91
|
+
interface Context {
|
|
92
|
+
logMetadata?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const t = initTRPC.context<Context>().create();
|
|
96
|
+
|
|
97
|
+
// Create a logged procedure using the same logger
|
|
98
|
+
const loggedProcedure = t.procedure.use(logger.trpc<Context>());
|
|
99
|
+
|
|
100
|
+
const appRouter = t.router({
|
|
101
|
+
hello: loggedProcedure
|
|
102
|
+
.input((val: unknown) => val as { name: string })
|
|
103
|
+
.query(({ input, ctx }) => {
|
|
104
|
+
// Attach metadata in tRPC
|
|
105
|
+
attachTrpcMetadata(ctx, {
|
|
106
|
+
greeted: input.name,
|
|
107
|
+
locale: 'en-US',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return { greeting: `Hello, ${input.name}!` };
|
|
111
|
+
}),
|
|
112
|
+
|
|
113
|
+
createUser: loggedProcedure
|
|
114
|
+
.input((val: unknown) => val as { email: string; password: string })
|
|
115
|
+
.mutation(({ input, ctx }) => {
|
|
116
|
+
const userId = Math.random().toString(36).slice(2);
|
|
117
|
+
|
|
118
|
+
// Attach user creation metadata
|
|
119
|
+
attachTrpcMetadata(ctx, {
|
|
120
|
+
newUserId: userId,
|
|
121
|
+
emailDomain: input.email.split('@')[1],
|
|
122
|
+
signupSource: 'api',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return { id: userId, email: input.email, created: true };
|
|
126
|
+
}),
|
|
127
|
+
|
|
128
|
+
processOrder: loggedProcedure
|
|
129
|
+
.input((val: unknown) => val as { items: string[]; priority: boolean })
|
|
130
|
+
.mutation(({ input, ctx }) => {
|
|
131
|
+
// Attach processing metadata
|
|
132
|
+
attachTrpcMetadata(ctx, {
|
|
133
|
+
itemCount: input.items.length,
|
|
134
|
+
priority: input.priority,
|
|
135
|
+
processingQueue: input.priority ? 'high' : 'normal',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return { processed: true, items: input.items.length };
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Mount tRPC
|
|
143
|
+
app.use(
|
|
144
|
+
'/trpc',
|
|
145
|
+
trpcExpress.createExpressMiddleware({
|
|
146
|
+
router: appRouter,
|
|
147
|
+
createContext: (): Context => ({ logMetadata: {} }),
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// Start Server
|
|
153
|
+
// ============================================================================
|
|
154
|
+
|
|
155
|
+
const PORT = 3000;
|
|
156
|
+
app.listen(PORT, () => {
|
|
157
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
158
|
+
console.log(`Logs written to: ./logs/app.log`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
export type AppRouter = typeof appRouter;
|
package/logo.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mohen",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Unified request/response logger for Express and tRPC with SSE support (墨痕 - ink trace)",
|
|
5
|
+
"main": "dist/logger.js",
|
|
6
|
+
"types": "dist/logger.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"test:watch": "vitest",
|
|
11
|
+
"example": "ts-node example/usage.ts"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"express",
|
|
15
|
+
"trpc",
|
|
16
|
+
"logger",
|
|
17
|
+
"logging",
|
|
18
|
+
"sse",
|
|
19
|
+
"middleware"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@trpc/server": "^10.0.0 || ^11.0.0",
|
|
27
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@trpc/server": "^10.45.0",
|
|
31
|
+
"@types/express": "^4.17.21",
|
|
32
|
+
"@types/node": "^20.10.0",
|
|
33
|
+
"@types/supertest": "^6.0.3",
|
|
34
|
+
"express": "^4.18.0",
|
|
35
|
+
"supertest": "^7.2.2",
|
|
36
|
+
"ts-node": "^10.9.0",
|
|
37
|
+
"typescript": "^5.3.0",
|
|
38
|
+
"vitest": "^4.0.17"
|
|
39
|
+
}
|
|
40
|
+
}
|