drtrace 0.2.0 → 0.4.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.
Files changed (53) hide show
  1. package/README.md +74 -4
  2. package/agents/CONTRIBUTING.md +296 -0
  3. package/agents/README.md +174 -0
  4. package/agents/daemon-method-selection.md +370 -0
  5. package/agents/integration-guides/cpp-best-practices.md +218 -0
  6. package/agents/integration-guides/cpp-ros-integration.md +88 -0
  7. package/agents/log-analysis.md +218 -0
  8. package/agents/log-help.md +226 -0
  9. package/agents/log-init.md +933 -0
  10. package/agents/log-it.md +1126 -0
  11. package/bin/init.js +4 -4
  12. package/dist/bin/init.js +31 -0
  13. package/dist/browser.d.ts +28 -0
  14. package/dist/browser.js +91 -0
  15. package/dist/config-schema.d.ts +2 -2
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/init.d.ts +44 -2
  19. package/dist/init.js +460 -30
  20. package/dist/logger.d.ts +7 -0
  21. package/dist/logger.js +30 -4
  22. package/dist/node.d.ts +13 -0
  23. package/dist/node.js +67 -0
  24. package/dist/resources/agents/CONTRIBUTING.md +296 -0
  25. package/dist/resources/agents/README.md +174 -0
  26. package/dist/resources/agents/daemon-method-selection.md +370 -0
  27. package/dist/resources/agents/integration-guides/cpp-best-practices.md +218 -0
  28. package/dist/resources/agents/integration-guides/cpp-ros-integration.md +88 -0
  29. package/dist/resources/agents/log-analysis.md +218 -0
  30. package/dist/resources/agents/log-help.md +226 -0
  31. package/dist/resources/agents/log-init.md +933 -0
  32. package/dist/resources/agents/log-it.md +1126 -0
  33. package/dist/resources/cpp/drtrace_sink.hpp +1249 -0
  34. package/dist/transport.js +5 -1
  35. package/dist/types.d.ts +8 -2
  36. package/package.json +28 -4
  37. package/.eslintrc.js +0 -20
  38. package/jest.config.js +0 -11
  39. package/src/client.ts +0 -68
  40. package/src/config-schema.ts +0 -115
  41. package/src/config.ts +0 -326
  42. package/src/index.ts +0 -3
  43. package/src/init.ts +0 -451
  44. package/src/logger.ts +0 -56
  45. package/src/queue.ts +0 -105
  46. package/src/transport.ts +0 -60
  47. package/src/types.ts +0 -20
  48. package/tests/client.test.ts +0 -66
  49. package/tests/config-schema.test.ts +0 -198
  50. package/tests/config.test.ts +0 -456
  51. package/tests/queue.test.ts +0 -72
  52. package/tests/transport.test.ts +0 -52
  53. package/tsconfig.json +0 -18
@@ -1,456 +0,0 @@
1
- /**
2
- * Tests for DrTrace configuration loader (JavaScript/TypeScript)
3
- */
4
-
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import { ConfigLoader, ConfigSchema, loadConfig } from '../src/config';
9
-
10
- describe('ConfigSchema', () => {
11
- describe('getDefault', () => {
12
- it('should return valid default configuration', () => {
13
- const defaults = ConfigSchema.getDefault();
14
- expect(defaults.project.name).toBe('my-app');
15
- expect(defaults.drtrace.applicationId).toBe('my-app');
16
- expect(defaults.drtrace.enabled).toBe(true);
17
- });
18
-
19
- it('should return a deep copy', () => {
20
- const defaults1 = ConfigSchema.getDefault();
21
- const defaults2 = ConfigSchema.getDefault();
22
- defaults1.project.name = 'modified';
23
- expect(defaults2.project.name).toBe('my-app');
24
- });
25
- });
26
-
27
- describe('validate', () => {
28
- it('should accept valid default config', () => {
29
- const config = ConfigSchema.getDefault();
30
- const validated = ConfigSchema.validate(config);
31
- expect(validated.project.name).toBe('my-app');
32
- });
33
-
34
- it('should reject missing project section', () => {
35
- const config = ConfigSchema.getDefault();
36
- delete (config as any).project;
37
- expect(() => ConfigSchema.validate(config)).toThrow('Missing required section: project');
38
- });
39
-
40
- it('should reject missing project name', () => {
41
- const config = ConfigSchema.getDefault();
42
- config.project.name = '' as any;
43
- expect(() => ConfigSchema.validate(config)).toThrow('Missing required field: project.name');
44
- });
45
-
46
- it('should reject invalid log level', () => {
47
- const config = ConfigSchema.getDefault();
48
- config.drtrace.logLevel = 'invalid' as any;
49
- expect(() => ConfigSchema.validate(config)).toThrow('Invalid logLevel');
50
- });
51
-
52
- it('should accept valid log levels', () => {
53
- for (const level of ['debug', 'info', 'warn', 'error']) {
54
- const config = ConfigSchema.getDefault();
55
- config.drtrace.logLevel = level as any;
56
- expect(() => ConfigSchema.validate(config)).not.toThrow();
57
- }
58
- });
59
-
60
- it('should reject invalid agent framework', () => {
61
- const config = ConfigSchema.getDefault();
62
- config.agent!.framework = 'invalid-framework' as any;
63
- expect(() => ConfigSchema.validate(config)).toThrow('Invalid agent framework');
64
- });
65
-
66
- it('should accept valid agent frameworks', () => {
67
- for (const framework of ['bmad', 'langchain', 'other']) {
68
- const config = ConfigSchema.getDefault();
69
- config.agent!.framework = framework as any;
70
- expect(() => ConfigSchema.validate(config)).not.toThrow();
71
- }
72
- });
73
-
74
- it('should reject invalid boolean for enabled', () => {
75
- const config = ConfigSchema.getDefault();
76
- config.drtrace.enabled = 'true' as any;
77
- expect(() => ConfigSchema.validate(config)).toThrow('Invalid type for drtrace.enabled');
78
- });
79
-
80
- it('should reject invalid type for batchSize', () => {
81
- const config = ConfigSchema.getDefault();
82
- config.drtrace.batchSize = 'fifty' as any;
83
- expect(() => ConfigSchema.validate(config)).toThrow('Invalid type for drtrace.batchSize');
84
- });
85
- });
86
- });
87
-
88
- describe('ConfigLoader', () => {
89
- let tmpDir: string;
90
-
91
- beforeEach(() => {
92
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'drtrace-config-test-'));
93
- // Clean up any existing env vars
94
- delete process.env.NODE_ENV;
95
- delete process.env.PYTHON_ENV;
96
- delete process.env.DRTRACE_APPLICATION_ID;
97
- delete process.env.DRTRACE_DAEMON_URL;
98
- delete process.env.DRTRACE_ENABLED;
99
- delete process.env.DRTRACE_LOG_LEVEL;
100
- delete process.env.DRTRACE_BATCH_SIZE;
101
- });
102
-
103
- afterEach(() => {
104
- // Clean up temp directory
105
- if (fs.existsSync(tmpDir)) {
106
- fs.rmSync(tmpDir, { recursive: true, force: true });
107
- }
108
- // Clean up env vars
109
- delete process.env.NODE_ENV;
110
- delete process.env.PYTHON_ENV;
111
- delete process.env.DRTRACE_APPLICATION_ID;
112
- delete process.env.DRTRACE_DAEMON_URL;
113
- delete process.env.DRTRACE_ENABLED;
114
- delete process.env.DRTRACE_LOG_LEVEL;
115
- delete process.env.DRTRACE_BATCH_SIZE;
116
- });
117
-
118
- describe('load', () => {
119
- it('should load defaults when no config files exist', () => {
120
- const config = ConfigLoader.load({ projectRoot: tmpDir });
121
- expect(config.project.name).toBe('my-app');
122
- expect(config.drtrace.applicationId).toBe('my-app');
123
- expect(config.drtrace.daemonUrl).toBe('http://localhost:8001');
124
- });
125
-
126
- it('should load from _drtrace/config.json', () => {
127
- const configDir = path.join(tmpDir, '_drtrace');
128
- fs.mkdirSync(configDir);
129
-
130
- const configData = {
131
- project: { name: 'my-custom-app' },
132
- drtrace: { applicationId: 'custom-app' },
133
- };
134
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
135
-
136
- const config = ConfigLoader.load({ projectRoot: tmpDir });
137
- expect(config.project.name).toBe('my-custom-app');
138
- expect(config.drtrace.applicationId).toBe('custom-app');
139
- });
140
-
141
- it('should load environment-specific override', () => {
142
- const configDir = path.join(tmpDir, '_drtrace');
143
- fs.mkdirSync(configDir);
144
-
145
- const baseConfig = {
146
- project: { name: 'my-app' },
147
- drtrace: {
148
- applicationId: 'my-app',
149
- daemonUrl: 'http://localhost:8001',
150
- enabled: true,
151
- },
152
- };
153
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
154
-
155
- const prodConfig = {
156
- drtrace: { daemonUrl: 'https://production.example.com' },
157
- };
158
- fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
159
-
160
- const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
161
- expect(config.drtrace.daemonUrl).toBe('https://production.example.com');
162
- expect(config.drtrace.applicationId).toBe('my-app'); // From base
163
- });
164
-
165
- it('should load environment subsection from base config', () => {
166
- const configDir = path.join(tmpDir, '_drtrace');
167
- fs.mkdirSync(configDir);
168
-
169
- const configData = {
170
- project: { name: 'my-app' },
171
- drtrace: {
172
- applicationId: 'my-app',
173
- daemonUrl: 'http://localhost:8001',
174
- enabled: true,
175
- },
176
- environment: {
177
- production: {
178
- daemonUrl: 'https://production.example.com',
179
- enabled: false,
180
- },
181
- },
182
- };
183
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
184
-
185
- const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
186
- expect(config.drtrace.daemonUrl).toBe('https://production.example.com');
187
- expect(config.drtrace.enabled).toBe(false);
188
- });
189
-
190
- it('should detect environment from NODE_ENV', () => {
191
- const configDir = path.join(tmpDir, '_drtrace');
192
- fs.mkdirSync(configDir);
193
-
194
- const baseConfig = {
195
- project: { name: 'my-app' },
196
- drtrace: { applicationId: 'my-app' },
197
- };
198
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
199
-
200
- const prodConfig = { drtrace: { enabled: false } };
201
- fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
202
-
203
- process.env.NODE_ENV = 'production';
204
- const config = ConfigLoader.load({ projectRoot: tmpDir });
205
- expect(config.drtrace.enabled).toBe(false);
206
- });
207
-
208
- it('should detect environment from PYTHON_ENV', () => {
209
- const configDir = path.join(tmpDir, '_drtrace');
210
- fs.mkdirSync(configDir);
211
-
212
- const baseConfig = {
213
- project: { name: 'my-app' },
214
- drtrace: { applicationId: 'my-app' },
215
- };
216
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
217
-
218
- const devConfig = { drtrace: { logLevel: 'debug' } };
219
- fs.writeFileSync(path.join(configDir, 'config.development.json'), JSON.stringify(devConfig));
220
-
221
- process.env.PYTHON_ENV = 'development';
222
- const config = ConfigLoader.load({ projectRoot: tmpDir });
223
- expect(config.drtrace.logLevel).toBe('debug');
224
- });
225
-
226
- it('should throw on invalid JSON', () => {
227
- const configDir = path.join(tmpDir, '_drtrace');
228
- fs.mkdirSync(configDir);
229
- fs.writeFileSync(path.join(configDir, 'config.json'), '{ invalid json }');
230
-
231
- expect(() => ConfigLoader.load({ projectRoot: tmpDir })).toThrow('Invalid JSON');
232
- });
233
- });
234
-
235
- describe('environment variable overrides', () => {
236
- it('should override applicationId from DRTRACE_APPLICATION_ID', () => {
237
- const configDir = path.join(tmpDir, '_drtrace');
238
- fs.mkdirSync(configDir);
239
-
240
- const configData = {
241
- project: { name: 'my-app' },
242
- drtrace: { applicationId: 'from-file' },
243
- };
244
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
245
-
246
- process.env.DRTRACE_APPLICATION_ID = 'from-env';
247
- const config = ConfigLoader.load({ projectRoot: tmpDir });
248
- expect(config.drtrace.applicationId).toBe('from-env');
249
- });
250
-
251
- it('should override daemonUrl from DRTRACE_DAEMON_URL', () => {
252
- const configDir = path.join(tmpDir, '_drtrace');
253
- fs.mkdirSync(configDir);
254
-
255
- const configData = {
256
- project: { name: 'my-app' },
257
- drtrace: {
258
- applicationId: 'my-app',
259
- daemonUrl: 'http://localhost:8001',
260
- },
261
- };
262
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
263
-
264
- process.env.DRTRACE_DAEMON_URL = 'http://custom:9000';
265
- const config = ConfigLoader.load({ projectRoot: tmpDir });
266
- expect(config.drtrace.daemonUrl).toBe('http://custom:9000');
267
- });
268
-
269
- it('should override enabled from DRTRACE_ENABLED=true', () => {
270
- const configDir = path.join(tmpDir, '_drtrace');
271
- fs.mkdirSync(configDir);
272
-
273
- const configData = {
274
- project: { name: 'my-app' },
275
- drtrace: { applicationId: 'my-app', enabled: false },
276
- };
277
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
278
-
279
- process.env.DRTRACE_ENABLED = 'true';
280
- const config = ConfigLoader.load({ projectRoot: tmpDir });
281
- expect(config.drtrace.enabled).toBe(true);
282
- });
283
-
284
- it('should override enabled from DRTRACE_ENABLED=false', () => {
285
- const configDir = path.join(tmpDir, '_drtrace');
286
- fs.mkdirSync(configDir);
287
-
288
- const configData = {
289
- project: { name: 'my-app' },
290
- drtrace: { applicationId: 'my-app', enabled: true },
291
- };
292
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
293
-
294
- process.env.DRTRACE_ENABLED = 'false';
295
- const config = ConfigLoader.load({ projectRoot: tmpDir });
296
- expect(config.drtrace.enabled).toBe(false);
297
- });
298
-
299
- it('should handle DRTRACE_ENABLED=1 as true', () => {
300
- const configDir = path.join(tmpDir, '_drtrace');
301
- fs.mkdirSync(configDir);
302
-
303
- const configData = {
304
- project: { name: 'my-app' },
305
- drtrace: { applicationId: 'my-app', enabled: false },
306
- };
307
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
308
-
309
- process.env.DRTRACE_ENABLED = '1';
310
- const config = ConfigLoader.load({ projectRoot: tmpDir });
311
- expect(config.drtrace.enabled).toBe(true);
312
- });
313
-
314
- it('should override batchSize from DRTRACE_BATCH_SIZE', () => {
315
- const configDir = path.join(tmpDir, '_drtrace');
316
- fs.mkdirSync(configDir);
317
-
318
- const configData = {
319
- project: { name: 'my-app' },
320
- drtrace: { applicationId: 'my-app', batchSize: 50 },
321
- };
322
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
323
-
324
- process.env.DRTRACE_BATCH_SIZE = '100';
325
- const config = ConfigLoader.load({ projectRoot: tmpDir });
326
- expect(config.drtrace.batchSize).toBe(100);
327
- });
328
-
329
- it('should throw on invalid DRTRACE_BATCH_SIZE', () => {
330
- const configDir = path.join(tmpDir, '_drtrace');
331
- fs.mkdirSync(configDir);
332
-
333
- const configData = {
334
- project: { name: 'my-app' },
335
- drtrace: { applicationId: 'my-app' },
336
- };
337
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
338
-
339
- process.env.DRTRACE_BATCH_SIZE = 'not-a-number';
340
- expect(() => ConfigLoader.load({ projectRoot: tmpDir })).toThrow('must be an integer');
341
- });
342
-
343
- it('should override agent framework from DRTRACE_AGENT_FRAMEWORK', () => {
344
- const configDir = path.join(tmpDir, '_drtrace');
345
- fs.mkdirSync(configDir);
346
-
347
- const configData = {
348
- project: { name: 'my-app' },
349
- drtrace: { applicationId: 'my-app' },
350
- agent: { framework: null },
351
- };
352
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
353
-
354
- process.env.DRTRACE_AGENT_FRAMEWORK = 'langchain';
355
- const config = ConfigLoader.load({ projectRoot: tmpDir });
356
- expect(config.agent?.framework).toBe('langchain');
357
- });
358
-
359
- it('should apply environment variables with highest priority', () => {
360
- const configDir = path.join(tmpDir, '_drtrace');
361
- fs.mkdirSync(configDir);
362
-
363
- const baseConfig = {
364
- project: { name: 'my-app' },
365
- drtrace: {
366
- applicationId: 'from-file',
367
- daemonUrl: 'http://file-daemon:8001',
368
- },
369
- };
370
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
371
-
372
- const prodConfig = { drtrace: { applicationId: 'from-prod-file' } };
373
- fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
374
-
375
- process.env.DRTRACE_APPLICATION_ID = 'from-env';
376
- process.env.DRTRACE_DAEMON_URL = 'http://env-daemon:9000';
377
-
378
- const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
379
- expect(config.drtrace.applicationId).toBe('from-env');
380
- expect(config.drtrace.daemonUrl).toBe('http://env-daemon:9000');
381
- });
382
- });
383
-
384
- describe('convenience function', () => {
385
- it('should work with loadConfig function', () => {
386
- const configDir = path.join(tmpDir, '_drtrace');
387
- fs.mkdirSync(configDir);
388
-
389
- const configData = {
390
- project: { name: 'my-app' },
391
- drtrace: { applicationId: 'test-app' },
392
- };
393
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
394
-
395
- const config = loadConfig({ projectRoot: tmpDir });
396
- expect(config.project.name).toBe('my-app');
397
- expect(config.drtrace.applicationId).toBe('test-app');
398
- });
399
- });
400
-
401
- describe('loading priority', () => {
402
- it('should respect complete loading priority', () => {
403
- const configDir = path.join(tmpDir, '_drtrace');
404
- fs.mkdirSync(configDir);
405
-
406
- const baseConfig = {
407
- project: { name: 'from-base' },
408
- drtrace: {
409
- applicationId: 'from-base',
410
- daemonUrl: 'http://localhost:8001',
411
- logLevel: 'info' as const,
412
- },
413
- };
414
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(baseConfig));
415
-
416
- const prodConfig = { drtrace: { logLevel: 'debug' as const } };
417
- fs.writeFileSync(path.join(configDir, 'config.production.json'), JSON.stringify(prodConfig));
418
-
419
- process.env.DRTRACE_DAEMON_URL = 'http://env-daemon:9000';
420
-
421
- const config = ConfigLoader.load({ projectRoot: tmpDir, environment: 'production' });
422
-
423
- // From env var (highest)
424
- expect(config.drtrace.daemonUrl).toBe('http://env-daemon:9000');
425
-
426
- // From env-specific override
427
- expect(config.drtrace.logLevel).toBe('debug');
428
-
429
- // From base config
430
- expect(config.drtrace.applicationId).toBe('from-base');
431
-
432
- // From defaults (for fields not specified anywhere)
433
- expect(config.drtrace.batchSize).toBe(50);
434
- });
435
- });
436
-
437
- describe('missing config files', () => {
438
- it('should use defaults when config files are missing', () => {
439
- const config = ConfigLoader.load({ projectRoot: tmpDir });
440
- expect(config.project.name).toBe('my-app');
441
- expect(config.drtrace.enabled).toBe(true);
442
- });
443
-
444
- it('should use defaults for missing drtrace section', () => {
445
- const configDir = path.join(tmpDir, '_drtrace');
446
- fs.mkdirSync(configDir);
447
-
448
- const configData = { project: { name: 'my-app' } };
449
- fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(configData));
450
-
451
- const config = ConfigLoader.load({ projectRoot: tmpDir });
452
- expect(config.project.name).toBe('my-app');
453
- expect(config.drtrace.applicationId).toBe('my-app'); // From defaults
454
- });
455
- });
456
- });
@@ -1,72 +0,0 @@
1
- import { LogQueue } from '../src/queue';
2
- import { Transport } from '../src/transport';
3
- import type { LogEvent } from '../src/types';
4
-
5
- jest.useFakeTimers();
6
-
7
- class MockTransport extends Transport {
8
- public sent: LogEvent[][] = [];
9
- constructor() {
10
- super({ daemonUrl: 'http://localhost:8001' });
11
- }
12
- async sendBatch(events: LogEvent[]): Promise<void> {
13
- this.sent.push(events);
14
- }
15
- }
16
-
17
- describe('LogQueue', () => {
18
- afterEach(() => {
19
- process.removeAllListeners('SIGINT');
20
- process.removeAllListeners('SIGTERM');
21
- });
22
-
23
- it('flushes when batchSize is reached', async () => {
24
- const transport = new MockTransport();
25
- const queue = new LogQueue({ transport, batchSize: 3, flushIntervalMs: 1000 });
26
- queue.start();
27
-
28
- const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
29
- queue.push(mk('a'));
30
- queue.push(mk('b'));
31
- expect(transport.sent.length).toBe(0);
32
- queue.push(mk('c'));
33
- expect(transport.sent.length).toBe(1);
34
- expect(transport.sent[0].map(e => e.message)).toEqual(['a', 'b', 'c']);
35
- queue.stop();
36
- });
37
-
38
- it('flushes periodically based on flushIntervalMs', async () => {
39
- const transport = new MockTransport();
40
- const queue = new LogQueue({ transport, batchSize: 10, flushIntervalMs: 500 });
41
- queue.start();
42
-
43
- const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
44
- queue.push(mk('x'));
45
-
46
- // Advance timers to trigger scheduled flush
47
- jest.advanceTimersByTime(500);
48
- // Allow flush promise to resolve
49
- await Promise.resolve();
50
-
51
- expect(transport.sent.length).toBe(1);
52
- expect(transport.sent[0].map(e => e.message)).toEqual(['x']);
53
- queue.stop();
54
- });
55
-
56
- it('enforces maxQueueSize and drops oldest with warning', async () => {
57
- const transport = new MockTransport();
58
- const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
59
- const queue = new LogQueue({ transport, batchSize: 100, flushIntervalMs: 1000, maxQueueSize: 2 });
60
- queue.start();
61
-
62
- const mk = (msg: string): LogEvent => ({ timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: msg });
63
- queue.push(mk('a'));
64
- queue.push(mk('b'));
65
- queue.push(mk('c')); // exceeds maxQueueSize
66
-
67
- expect(queue as any).toBeDefined();
68
- expect(warnSpy).toHaveBeenCalled();
69
- warnSpy.mockRestore();
70
- queue.stop();
71
- });
72
- });
@@ -1,52 +0,0 @@
1
- import { Transport } from '../src/transport';
2
-
3
- jest.useFakeTimers();
4
-
5
- // Mock fetch globally
6
- const mockFetch = jest.fn(async () => ({ ok: true, body: { pipe: () => {} }, text: async () => '' } as any));
7
- (global as any).fetch = mockFetch as any;
8
-
9
- describe('Transport', () => {
10
- beforeEach(() => {
11
- mockFetch.mockClear();
12
- });
13
-
14
- afterAll(() => {
15
- jest.useRealTimers();
16
- });
17
-
18
- it('sends batched logs via fetch', async () => {
19
- const t = new Transport({ daemonUrl: 'http://daemon' });
20
- await t.sendBatch([
21
- { timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: 'hello' },
22
- ]);
23
- expect(mockFetch).toHaveBeenCalledTimes(1);
24
- const url = (mockFetch.mock.calls[0] as any)[0];
25
- expect(url).toBe('http://daemon/logs/ingest');
26
- });
27
-
28
- it('retries on failure with backoff', async () => {
29
- const seq = [
30
- Promise.reject(new Error('fail1')),
31
- Promise.resolve({ ok: false, body: null, text: async () => '' } as any),
32
- Promise.resolve({ ok: true, body: null, text: async () => '' } as any),
33
- ];
34
- mockFetch.mockImplementation(() => seq.shift()!);
35
-
36
- const t = new Transport({ daemonUrl: 'http://daemon', maxRetries: 2, timeoutMs: 1000 });
37
- const sendPromise = t.sendBatch([
38
- { timestamp: new Date().toISOString(), applicationId: 'app', level: 'info', message: 'hello' },
39
- ]);
40
-
41
- jest.advanceTimersByTime(1000);
42
- await jest.runAllTimersAsync();
43
- await sendPromise;
44
- expect(mockFetch).toHaveBeenCalledTimes(3);
45
- });
46
-
47
- it('ignores empty batches', async () => {
48
- const t = new Transport({ daemonUrl: 'http://daemon' });
49
- await t.sendBatch([]);
50
- expect(mockFetch).toHaveBeenCalledTimes(0);
51
- });
52
- });
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "commonjs",
5
- "lib": ["ES2020"],
6
- "declaration": true,
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "moduleResolution": "node"
15
- },
16
- "include": ["src/**/*"],
17
- "exclude": ["node_modules", "dist", "tests"]
18
- }