@tellann/backend-sdk 0.1.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/README.md +111 -0
- package/dist/core/SOTS.d.ts +39 -0
- package/dist/core/SOTS.js +158 -0
- package/dist/core/TELLANN.d.ts +39 -0
- package/dist/core/TELLANN.js +158 -0
- package/dist/core/captureError.d.ts +11 -0
- package/dist/core/captureError.js +63 -0
- package/dist/core/trackApi.d.ts +14 -0
- package/dist/core/trackApi.js +63 -0
- package/dist/core/trackState.d.ts +10 -0
- package/dist/core/trackState.js +61 -0
- package/dist/core/workflowTracker.d.ts +19 -0
- package/dist/core/workflowTracker.js +73 -0
- package/dist/event-types.d.ts +17 -0
- package/dist/event-types.js +2 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +41 -0
- package/dist/integrations/express/index.d.ts +31 -0
- package/dist/integrations/express/index.js +83 -0
- package/dist/integrations/fastify/index.d.ts +13 -0
- package/dist/integrations/fastify/index.js +61 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @tellann/backend-sdk
|
|
2
|
+
|
|
3
|
+
Server-side telemetry and QA-run correlation SDK for Tellann. Tracks API requests,
|
|
4
|
+
server errors, state transitions, and workflow lifecycle events, and correlates them
|
|
5
|
+
with a frontend session/run/trace via inbound headers. Ships optional Express and
|
|
6
|
+
Fastify integrations.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @tellann/backend-sdk
|
|
12
|
+
# or
|
|
13
|
+
pnpm add @tellann/backend-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`express` and `fastify` are optional peer dependencies — install whichever framework
|
|
17
|
+
you use. The core API works without either.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { TELLANN } from '@tellann/backend-sdk';
|
|
23
|
+
|
|
24
|
+
TELLANN.initialize({
|
|
25
|
+
endpoint: 'https://collector.example.com',
|
|
26
|
+
applicationId: 'my-api',
|
|
27
|
+
environmentId: 'production',
|
|
28
|
+
apiKey: '<server-key>', // optional
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await TELLANN.trackApi({
|
|
32
|
+
endpoint: '/orders',
|
|
33
|
+
method: 'POST',
|
|
34
|
+
statusCode: 201,
|
|
35
|
+
durationMs: 42,
|
|
36
|
+
sessionId, // optional, from x-tellann-session-id
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await TELLANN.captureError({ error, eventType: 'SERVER_ERROR' });
|
|
40
|
+
await TELLANN.trackState({ /* TrackStateOptions */ });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Standalone helper functions are also exported and operate on the initialized singleton:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { trackApi, captureError, trackState } from '@tellann/backend-sdk';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Configuration (`TellannBackendConfig`)
|
|
50
|
+
|
|
51
|
+
| Option | Type | Notes |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `endpoint` | `string` | Collector base URL. |
|
|
54
|
+
| `applicationId` | `string` | Identifies the service. |
|
|
55
|
+
| `tenantId` | `string` | Optional; defaults to `'unknown'`. |
|
|
56
|
+
| `environmentId` | `string` | Optional environment identifier. |
|
|
57
|
+
| `apiKey` | `string` | Optional; sent as a bearer token. |
|
|
58
|
+
| `runId` / `sessionId` / `traceId` | `string` | Optional correlation IDs. |
|
|
59
|
+
| `agentVersion` / `instrumentationManifestVersion` | `string` | Optional correlation metadata. |
|
|
60
|
+
|
|
61
|
+
Individual events are capped at 32 KB.
|
|
62
|
+
|
|
63
|
+
## Express integration
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import express from 'express';
|
|
67
|
+
import {
|
|
68
|
+
tellannExpressMiddleware,
|
|
69
|
+
tellannExpressErrorHandler,
|
|
70
|
+
} from '@tellann/backend-sdk';
|
|
71
|
+
|
|
72
|
+
const app = express();
|
|
73
|
+
|
|
74
|
+
app.use(tellannExpressMiddleware()); // extracts correlation context, times requests
|
|
75
|
+
// ... your routes ...
|
|
76
|
+
app.use(tellannExpressErrorHandler()); // captures errors (mount last)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The middleware populates `req.tellann` with `{ sessionId, runId, traceId }` extracted
|
|
80
|
+
from `x-tellann-session-id`, `x-tellann-run-id`, `x-tellann-trace-id`, and W3C
|
|
81
|
+
`traceparent` headers.
|
|
82
|
+
|
|
83
|
+
## Fastify integration
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import Fastify from 'fastify';
|
|
87
|
+
import { tellannFastifyPlugin } from '@tellann/backend-sdk';
|
|
88
|
+
|
|
89
|
+
const fastify = Fastify();
|
|
90
|
+
await fastify.register(tellannFastifyPlugin);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The plugin adds `onResponse` and `onError` hooks that emit `API_REQUEST` and
|
|
94
|
+
`SERVER_ERROR` events with timing and correlation data.
|
|
95
|
+
|
|
96
|
+
## Workflows
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const id = TELLANN.startWorkflow('nightly-reconcile', sessionId);
|
|
100
|
+
await TELLANN.completeWorkflow(id, sessionId);
|
|
101
|
+
// or failWorkflow(id, sessionId, reason)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## TypeScript
|
|
105
|
+
|
|
106
|
+
Types ship with the package. `TellannBackendConfig`, `TrackApiOptions`,
|
|
107
|
+
`CaptureErrorOptions`, `TrackStateOptions`, `EventType`, and `TellannEvent` are exported.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
`UNLICENSED` — see the repository for terms.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TrackApiOptions } from './trackApi';
|
|
2
|
+
import { CaptureErrorOptions } from './captureError';
|
|
3
|
+
import { TrackStateOptions } from './trackState';
|
|
4
|
+
import { BackendWorkflowTracker } from './workflowTracker';
|
|
5
|
+
import type { EventType } from '../event-types';
|
|
6
|
+
export interface SotsBackendConfig {
|
|
7
|
+
endpoint: string;
|
|
8
|
+
tenantId?: string;
|
|
9
|
+
applicationId: string;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
environmentId?: string;
|
|
12
|
+
runId?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
traceId?: string;
|
|
15
|
+
agentVersion?: string;
|
|
16
|
+
instrumentationManifestVersion?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class SOTSBackend {
|
|
19
|
+
private config;
|
|
20
|
+
private workflowTracker;
|
|
21
|
+
initialize(config: SotsBackendConfig): void;
|
|
22
|
+
getConfig(): SotsBackendConfig | null;
|
|
23
|
+
isInitialized(): boolean;
|
|
24
|
+
trackApi(options: TrackApiOptions): Promise<void>;
|
|
25
|
+
captureError(options: CaptureErrorOptions): Promise<void>;
|
|
26
|
+
trackState(options: TrackStateOptions): Promise<void>;
|
|
27
|
+
trackEvent(eventType: EventType, metadata?: Record<string, any>, sessionId?: string): Promise<void>;
|
|
28
|
+
verifyInstallation(sessionId?: string): Promise<void>;
|
|
29
|
+
startWorkflow(workflowName: string, sessionId?: string): string;
|
|
30
|
+
completeWorkflow(workflowId: string, sessionId?: string): Promise<void>;
|
|
31
|
+
failWorkflow(workflowId: string, reason?: string, sessionId?: string): Promise<void>;
|
|
32
|
+
abandonWorkflow(workflowId: string): void;
|
|
33
|
+
cancelWorkflow(workflowId: string, reason?: string, sessionId?: string): Promise<void>;
|
|
34
|
+
captureMessage(message: string, severity?: string, sessionId?: string): Promise<void>;
|
|
35
|
+
private sendEvent;
|
|
36
|
+
teardown(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare const SOTS: SOTSBackend;
|
|
39
|
+
export { BackendWorkflowTracker };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BackendWorkflowTracker = exports.SOTS = exports.SOTSBackend = void 0;
|
|
4
|
+
const trackApi_1 = require("./trackApi");
|
|
5
|
+
const captureError_1 = require("./captureError");
|
|
6
|
+
const trackState_1 = require("./trackState");
|
|
7
|
+
const workflowTracker_1 = require("./workflowTracker");
|
|
8
|
+
Object.defineProperty(exports, "BackendWorkflowTracker", { enumerable: true, get: function () { return workflowTracker_1.BackendWorkflowTracker; } });
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
class SOTSBackend {
|
|
11
|
+
config = null;
|
|
12
|
+
workflowTracker = new workflowTracker_1.BackendWorkflowTracker();
|
|
13
|
+
initialize(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
console.log('[Tellann Backend] Initialized');
|
|
16
|
+
}
|
|
17
|
+
getConfig() {
|
|
18
|
+
return this.config;
|
|
19
|
+
}
|
|
20
|
+
isInitialized() {
|
|
21
|
+
return this.config !== null;
|
|
22
|
+
}
|
|
23
|
+
async trackApi(options) {
|
|
24
|
+
if (!this.config)
|
|
25
|
+
return;
|
|
26
|
+
await (0, trackApi_1.trackApiEvent)(this.config, options);
|
|
27
|
+
}
|
|
28
|
+
async captureError(options) {
|
|
29
|
+
if (!this.config)
|
|
30
|
+
return;
|
|
31
|
+
await (0, captureError_1.captureErrorEvent)(this.config, options);
|
|
32
|
+
}
|
|
33
|
+
async trackState(options) {
|
|
34
|
+
if (!this.config)
|
|
35
|
+
return;
|
|
36
|
+
await (0, trackState_1.trackStateEvent)(this.config, options);
|
|
37
|
+
}
|
|
38
|
+
async trackEvent(eventType, metadata = {}, sessionId) {
|
|
39
|
+
await this.sendEvent(eventType, sessionId, metadata);
|
|
40
|
+
}
|
|
41
|
+
async verifyInstallation(sessionId) {
|
|
42
|
+
await this.trackEvent('TELLANN_INITIALIZED', {
|
|
43
|
+
source: 'manual_verification',
|
|
44
|
+
verificationKind: 'BOOTSTRAP_INITIALIZED',
|
|
45
|
+
}, sessionId);
|
|
46
|
+
}
|
|
47
|
+
startWorkflow(workflowName, sessionId) {
|
|
48
|
+
const id = this.workflowTracker.start(workflowName);
|
|
49
|
+
this.sendEvent('WORKFLOW_STARTED', sessionId, {
|
|
50
|
+
workflowId: id,
|
|
51
|
+
workflowName,
|
|
52
|
+
});
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
async completeWorkflow(workflowId, sessionId) {
|
|
56
|
+
const result = this.workflowTracker.complete(workflowId);
|
|
57
|
+
if (result) {
|
|
58
|
+
await this.sendEvent('WORKFLOW_COMPLETED', sessionId, {
|
|
59
|
+
workflowId,
|
|
60
|
+
workflowName: result.name,
|
|
61
|
+
durationMs: result.durationMs,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async failWorkflow(workflowId, reason, sessionId) {
|
|
66
|
+
const result = this.workflowTracker.fail(workflowId);
|
|
67
|
+
if (result) {
|
|
68
|
+
await this.sendEvent('WORKFLOW_FAILED', sessionId, {
|
|
69
|
+
workflowId,
|
|
70
|
+
workflowName: result.name,
|
|
71
|
+
durationMs: result.durationMs,
|
|
72
|
+
reason: reason || 'Unknown error',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
abandonWorkflow(workflowId) {
|
|
77
|
+
this.workflowTracker.abandon(workflowId);
|
|
78
|
+
}
|
|
79
|
+
async cancelWorkflow(workflowId, reason, sessionId) {
|
|
80
|
+
const result = this.workflowTracker.fail(workflowId);
|
|
81
|
+
if (result) {
|
|
82
|
+
await this.sendEvent('WORKFLOW_CANCELLED', sessionId, {
|
|
83
|
+
workflowId,
|
|
84
|
+
workflowName: result.name,
|
|
85
|
+
durationMs: result.durationMs,
|
|
86
|
+
reason: reason ?? 'Cancelled',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async captureMessage(message, severity, sessionId) {
|
|
91
|
+
await this.sendEvent('SERVER_ERROR', sessionId, {
|
|
92
|
+
message,
|
|
93
|
+
severity: severity || 'error',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async sendEvent(eventType, sessionId, metadata) {
|
|
97
|
+
if (!this.config)
|
|
98
|
+
return;
|
|
99
|
+
const event = {
|
|
100
|
+
eventId: (0, uuid_1.v4)(),
|
|
101
|
+
sessionId: sessionId ?? this.config.sessionId ?? (0, uuid_1.v4)(),
|
|
102
|
+
tenantId: this.config.tenantId ?? 'unknown',
|
|
103
|
+
applicationId: this.config.applicationId,
|
|
104
|
+
environmentId: this.config.environmentId ?? null,
|
|
105
|
+
runId: this.config.runId ?? null,
|
|
106
|
+
traceId: this.config.traceId ?? null,
|
|
107
|
+
agentVersion: this.config.agentVersion ?? null,
|
|
108
|
+
instrumentationManifestVersion: this.config.instrumentationManifestVersion ?? null,
|
|
109
|
+
source: 'backend-sdk',
|
|
110
|
+
eventVersion: '1.0',
|
|
111
|
+
eventType: eventType,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
metadata,
|
|
114
|
+
};
|
|
115
|
+
// Enforce size limit
|
|
116
|
+
try {
|
|
117
|
+
const eventJson = JSON.stringify(event);
|
|
118
|
+
const eventSize = Buffer.byteLength(eventJson, 'utf8');
|
|
119
|
+
if (eventSize > 32 * 1024) {
|
|
120
|
+
console.error(`[Tellann Backend] Event of type "${eventType}" discarded. Size (${eventSize} bytes) exceeds limit of 32 KB.`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
129
|
+
if (this.config.apiKey) {
|
|
130
|
+
headers.Authorization = `Bearer ${this.config.apiKey}`;
|
|
131
|
+
}
|
|
132
|
+
if (this.config.environmentId) {
|
|
133
|
+
headers['x-sots-environment-id'] = this.config.environmentId;
|
|
134
|
+
}
|
|
135
|
+
if (this.config.runId)
|
|
136
|
+
headers['x-tellann-run-id'] = this.config.runId;
|
|
137
|
+
if (sessionId ?? this.config.sessionId)
|
|
138
|
+
headers['x-tellann-session-id'] = sessionId ?? this.config.sessionId;
|
|
139
|
+
if (this.config.traceId)
|
|
140
|
+
headers['x-tellann-trace-id'] = this.config.traceId;
|
|
141
|
+
await fetch(`${this.config.endpoint}/v1/events`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(event),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Swallowed
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Allow teardown to clean up intervals/tracker memory
|
|
152
|
+
teardown() {
|
|
153
|
+
this.workflowTracker.destroy();
|
|
154
|
+
this.config = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.SOTSBackend = SOTSBackend;
|
|
158
|
+
exports.SOTS = new SOTSBackend();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TrackApiOptions } from './trackApi';
|
|
2
|
+
import { CaptureErrorOptions } from './captureError';
|
|
3
|
+
import { TrackStateOptions } from './trackState';
|
|
4
|
+
import { BackendWorkflowTracker } from './workflowTracker';
|
|
5
|
+
import type { EventType } from '../event-types';
|
|
6
|
+
export interface TellannBackendConfig {
|
|
7
|
+
endpoint: string;
|
|
8
|
+
tenantId?: string;
|
|
9
|
+
applicationId: string;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
environmentId?: string;
|
|
12
|
+
runId?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
traceId?: string;
|
|
15
|
+
agentVersion?: string;
|
|
16
|
+
instrumentationManifestVersion?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class TELLANNBackend {
|
|
19
|
+
private config;
|
|
20
|
+
private workflowTracker;
|
|
21
|
+
initialize(config: TellannBackendConfig): void;
|
|
22
|
+
getConfig(): TellannBackendConfig | null;
|
|
23
|
+
isInitialized(): boolean;
|
|
24
|
+
trackApi(options: TrackApiOptions): Promise<void>;
|
|
25
|
+
captureError(options: CaptureErrorOptions): Promise<void>;
|
|
26
|
+
trackState(options: TrackStateOptions): Promise<void>;
|
|
27
|
+
trackEvent(eventType: EventType, metadata?: Record<string, any>, sessionId?: string): Promise<void>;
|
|
28
|
+
verifyInstallation(sessionId?: string): Promise<void>;
|
|
29
|
+
startWorkflow(workflowName: string, sessionId?: string): string;
|
|
30
|
+
completeWorkflow(workflowId: string, sessionId?: string): Promise<void>;
|
|
31
|
+
failWorkflow(workflowId: string, reason?: string, sessionId?: string): Promise<void>;
|
|
32
|
+
abandonWorkflow(workflowId: string): void;
|
|
33
|
+
cancelWorkflow(workflowId: string, reason?: string, sessionId?: string): Promise<void>;
|
|
34
|
+
captureMessage(message: string, severity?: string, sessionId?: string): Promise<void>;
|
|
35
|
+
private sendEvent;
|
|
36
|
+
teardown(): void;
|
|
37
|
+
}
|
|
38
|
+
export declare const TELLANN: TELLANNBackend;
|
|
39
|
+
export { BackendWorkflowTracker };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BackendWorkflowTracker = exports.TELLANN = exports.TELLANNBackend = void 0;
|
|
4
|
+
const trackApi_1 = require("./trackApi");
|
|
5
|
+
const captureError_1 = require("./captureError");
|
|
6
|
+
const trackState_1 = require("./trackState");
|
|
7
|
+
const workflowTracker_1 = require("./workflowTracker");
|
|
8
|
+
Object.defineProperty(exports, "BackendWorkflowTracker", { enumerable: true, get: function () { return workflowTracker_1.BackendWorkflowTracker; } });
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
class TELLANNBackend {
|
|
11
|
+
config = null;
|
|
12
|
+
workflowTracker = new workflowTracker_1.BackendWorkflowTracker();
|
|
13
|
+
initialize(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
console.log('[Tellann Backend] Initialized');
|
|
16
|
+
}
|
|
17
|
+
getConfig() {
|
|
18
|
+
return this.config;
|
|
19
|
+
}
|
|
20
|
+
isInitialized() {
|
|
21
|
+
return this.config !== null;
|
|
22
|
+
}
|
|
23
|
+
async trackApi(options) {
|
|
24
|
+
if (!this.config)
|
|
25
|
+
return;
|
|
26
|
+
await (0, trackApi_1.trackApiEvent)(this.config, options);
|
|
27
|
+
}
|
|
28
|
+
async captureError(options) {
|
|
29
|
+
if (!this.config)
|
|
30
|
+
return;
|
|
31
|
+
await (0, captureError_1.captureErrorEvent)(this.config, options);
|
|
32
|
+
}
|
|
33
|
+
async trackState(options) {
|
|
34
|
+
if (!this.config)
|
|
35
|
+
return;
|
|
36
|
+
await (0, trackState_1.trackStateEvent)(this.config, options);
|
|
37
|
+
}
|
|
38
|
+
async trackEvent(eventType, metadata = {}, sessionId) {
|
|
39
|
+
await this.sendEvent(eventType, sessionId, metadata);
|
|
40
|
+
}
|
|
41
|
+
async verifyInstallation(sessionId) {
|
|
42
|
+
await this.trackEvent('TELLANN_INITIALIZED', {
|
|
43
|
+
source: 'manual_verification',
|
|
44
|
+
verificationKind: 'BOOTSTRAP_INITIALIZED',
|
|
45
|
+
}, sessionId);
|
|
46
|
+
}
|
|
47
|
+
startWorkflow(workflowName, sessionId) {
|
|
48
|
+
const id = this.workflowTracker.start(workflowName);
|
|
49
|
+
this.sendEvent('WORKFLOW_STARTED', sessionId, {
|
|
50
|
+
workflowId: id,
|
|
51
|
+
workflowName,
|
|
52
|
+
});
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
async completeWorkflow(workflowId, sessionId) {
|
|
56
|
+
const result = this.workflowTracker.complete(workflowId);
|
|
57
|
+
if (result) {
|
|
58
|
+
await this.sendEvent('WORKFLOW_COMPLETED', sessionId, {
|
|
59
|
+
workflowId,
|
|
60
|
+
workflowName: result.name,
|
|
61
|
+
durationMs: result.durationMs,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async failWorkflow(workflowId, reason, sessionId) {
|
|
66
|
+
const result = this.workflowTracker.fail(workflowId);
|
|
67
|
+
if (result) {
|
|
68
|
+
await this.sendEvent('WORKFLOW_FAILED', sessionId, {
|
|
69
|
+
workflowId,
|
|
70
|
+
workflowName: result.name,
|
|
71
|
+
durationMs: result.durationMs,
|
|
72
|
+
reason: reason || 'Unknown error',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
abandonWorkflow(workflowId) {
|
|
77
|
+
this.workflowTracker.abandon(workflowId);
|
|
78
|
+
}
|
|
79
|
+
async cancelWorkflow(workflowId, reason, sessionId) {
|
|
80
|
+
const result = this.workflowTracker.fail(workflowId);
|
|
81
|
+
if (result) {
|
|
82
|
+
await this.sendEvent('WORKFLOW_CANCELLED', sessionId, {
|
|
83
|
+
workflowId,
|
|
84
|
+
workflowName: result.name,
|
|
85
|
+
durationMs: result.durationMs,
|
|
86
|
+
reason: reason ?? 'Cancelled',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async captureMessage(message, severity, sessionId) {
|
|
91
|
+
await this.sendEvent('SERVER_ERROR', sessionId, {
|
|
92
|
+
message,
|
|
93
|
+
severity: severity || 'error',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async sendEvent(eventType, sessionId, metadata) {
|
|
97
|
+
if (!this.config)
|
|
98
|
+
return;
|
|
99
|
+
const event = {
|
|
100
|
+
eventId: (0, uuid_1.v4)(),
|
|
101
|
+
sessionId: sessionId ?? this.config.sessionId ?? (0, uuid_1.v4)(),
|
|
102
|
+
tenantId: this.config.tenantId ?? 'unknown',
|
|
103
|
+
applicationId: this.config.applicationId,
|
|
104
|
+
environmentId: this.config.environmentId ?? null,
|
|
105
|
+
runId: this.config.runId ?? null,
|
|
106
|
+
traceId: this.config.traceId ?? null,
|
|
107
|
+
agentVersion: this.config.agentVersion ?? null,
|
|
108
|
+
instrumentationManifestVersion: this.config.instrumentationManifestVersion ?? null,
|
|
109
|
+
source: 'backend-sdk',
|
|
110
|
+
eventVersion: '1.0',
|
|
111
|
+
eventType: eventType,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
metadata,
|
|
114
|
+
};
|
|
115
|
+
// Enforce size limit
|
|
116
|
+
try {
|
|
117
|
+
const eventJson = JSON.stringify(event);
|
|
118
|
+
const eventSize = Buffer.byteLength(eventJson, 'utf8');
|
|
119
|
+
if (eventSize > 32 * 1024) {
|
|
120
|
+
console.error(`[Tellann Backend] Event of type "${eventType}" discarded. Size (${eventSize} bytes) exceeds limit of 32 KB.`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
129
|
+
if (this.config.apiKey) {
|
|
130
|
+
headers.Authorization = `Bearer ${this.config.apiKey}`;
|
|
131
|
+
}
|
|
132
|
+
if (this.config.environmentId) {
|
|
133
|
+
headers['x-tellann-environment-id'] = this.config.environmentId;
|
|
134
|
+
}
|
|
135
|
+
if (this.config.runId)
|
|
136
|
+
headers['x-tellann-run-id'] = this.config.runId;
|
|
137
|
+
if (sessionId ?? this.config.sessionId)
|
|
138
|
+
headers['x-tellann-session-id'] = sessionId ?? this.config.sessionId;
|
|
139
|
+
if (this.config.traceId)
|
|
140
|
+
headers['x-tellann-trace-id'] = this.config.traceId;
|
|
141
|
+
await fetch(`${this.config.endpoint}/v1/events`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(event),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Swallowed
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Allow teardown to clean up intervals/tracker memory
|
|
152
|
+
teardown() {
|
|
153
|
+
this.workflowTracker.destroy();
|
|
154
|
+
this.config = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.TELLANNBackend = TELLANNBackend;
|
|
158
|
+
exports.TELLANN = new TELLANNBackend();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TellannBackendConfig } from './TELLANN';
|
|
2
|
+
export interface CaptureErrorOptions {
|
|
3
|
+
error: Error | unknown;
|
|
4
|
+
context?: Record<string, any>;
|
|
5
|
+
/** Optional: link to a frontend session */
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
eventType?: 'SERVER_ERROR' | 'ERROR_OCCURRED';
|
|
8
|
+
runId?: string;
|
|
9
|
+
traceId?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function captureErrorEvent(config: TellannBackendConfig, options: CaptureErrorOptions): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureErrorEvent = captureErrorEvent;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const MAX_EVENT_SIZE_BYTES = 32 * 1024; // 32 KB limit
|
|
6
|
+
async function captureErrorEvent(config, options) {
|
|
7
|
+
const err = options.error instanceof Error ? options.error : new Error(String(options.error));
|
|
8
|
+
const event = {
|
|
9
|
+
eventId: (0, uuid_1.v4)(),
|
|
10
|
+
sessionId: options.sessionId ?? config.sessionId ?? (0, uuid_1.v4)(),
|
|
11
|
+
tenantId: config.tenantId ?? 'unknown',
|
|
12
|
+
applicationId: config.applicationId,
|
|
13
|
+
environmentId: config.environmentId ?? null,
|
|
14
|
+
runId: options.runId ?? config.runId ?? null,
|
|
15
|
+
traceId: options.traceId ?? config.traceId ?? null,
|
|
16
|
+
agentVersion: config.agentVersion ?? null,
|
|
17
|
+
instrumentationManifestVersion: config.instrumentationManifestVersion ?? null,
|
|
18
|
+
source: 'backend-sdk',
|
|
19
|
+
eventVersion: '1.0',
|
|
20
|
+
eventType: options.eventType ?? 'SERVER_ERROR',
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
metadata: {
|
|
23
|
+
message: err.message,
|
|
24
|
+
stack: err.stack ?? null,
|
|
25
|
+
name: err.name,
|
|
26
|
+
context: options.context ?? {},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
// Enforce Size Limit
|
|
30
|
+
try {
|
|
31
|
+
const eventJson = JSON.stringify(event);
|
|
32
|
+
const eventSize = Buffer.byteLength(eventJson, 'utf8');
|
|
33
|
+
if (eventSize > MAX_EVENT_SIZE_BYTES) {
|
|
34
|
+
console.error(`[Tellann Backend] Error event discarded. Size (${eventSize} bytes) exceeds limit of ${MAX_EVENT_SIZE_BYTES} bytes.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error('[Tellann Backend] Failed to compute size of error event, discarding', err);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
44
|
+
if (config.apiKey) {
|
|
45
|
+
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
46
|
+
}
|
|
47
|
+
if (config.environmentId) {
|
|
48
|
+
headers['x-tellann-environment-id'] = config.environmentId;
|
|
49
|
+
}
|
|
50
|
+
if (config.runId)
|
|
51
|
+
headers['x-tellann-run-id'] = config.runId;
|
|
52
|
+
if (config.traceId)
|
|
53
|
+
headers['x-tellann-trace-id'] = config.traceId;
|
|
54
|
+
await fetch(`${config.endpoint}/v1/events`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify(event),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Silently swallow
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TellannBackendConfig } from './TELLANN';
|
|
2
|
+
export interface TrackApiOptions {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
method: string;
|
|
5
|
+
statusCode: number;
|
|
6
|
+
durationMs: number;
|
|
7
|
+
/** Optional: correlate with a frontend session via X-TELLANN-Session-ID header */
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
/** Optional: idempotency / tracing */
|
|
10
|
+
requestId?: string;
|
|
11
|
+
runId?: string;
|
|
12
|
+
traceId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function trackApiEvent(config: TellannBackendConfig, options: TrackApiOptions): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackApiEvent = trackApiEvent;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const MAX_EVENT_SIZE_BYTES = 32 * 1024; // 32 KB limit
|
|
6
|
+
async function trackApiEvent(config, options) {
|
|
7
|
+
const event = {
|
|
8
|
+
eventId: (0, uuid_1.v4)(),
|
|
9
|
+
sessionId: options.sessionId ?? config.sessionId ?? (0, uuid_1.v4)(),
|
|
10
|
+
tenantId: config.tenantId ?? 'unknown',
|
|
11
|
+
applicationId: config.applicationId,
|
|
12
|
+
environmentId: config.environmentId ?? null,
|
|
13
|
+
runId: options.runId ?? config.runId ?? null,
|
|
14
|
+
traceId: options.traceId ?? config.traceId ?? null,
|
|
15
|
+
agentVersion: config.agentVersion ?? null,
|
|
16
|
+
instrumentationManifestVersion: config.instrumentationManifestVersion ?? null,
|
|
17
|
+
source: 'backend-sdk',
|
|
18
|
+
eventVersion: '1.0',
|
|
19
|
+
eventType: 'API_REQUEST',
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
metadata: {
|
|
22
|
+
requestId: options.requestId ?? (0, uuid_1.v4)(),
|
|
23
|
+
endpoint: options.endpoint,
|
|
24
|
+
method: options.method.toUpperCase(),
|
|
25
|
+
statusCode: options.statusCode,
|
|
26
|
+
durationMs: options.durationMs,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
// Enforce Size Limit
|
|
30
|
+
try {
|
|
31
|
+
const eventJson = JSON.stringify(event);
|
|
32
|
+
const eventSize = Buffer.byteLength(eventJson, 'utf8');
|
|
33
|
+
if (eventSize > MAX_EVENT_SIZE_BYTES) {
|
|
34
|
+
console.error(`[Tellann Backend] API request event discarded. Size (${eventSize} bytes) exceeds limit of ${MAX_EVENT_SIZE_BYTES} bytes.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error('[Tellann Backend] Failed to compute size of API event, discarding', err);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
44
|
+
if (config.apiKey) {
|
|
45
|
+
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
46
|
+
}
|
|
47
|
+
if (config.environmentId) {
|
|
48
|
+
headers['x-tellann-environment-id'] = config.environmentId;
|
|
49
|
+
}
|
|
50
|
+
if (config.runId)
|
|
51
|
+
headers['x-tellann-run-id'] = config.runId;
|
|
52
|
+
if (config.traceId)
|
|
53
|
+
headers['x-tellann-trace-id'] = config.traceId;
|
|
54
|
+
await fetch(`${config.endpoint}/v1/events`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify(event),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Silently swallow
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TellannBackendConfig } from './TELLANN';
|
|
2
|
+
export interface TrackStateOptions {
|
|
3
|
+
stateName: string;
|
|
4
|
+
category?: 'BUSINESS' | 'NAVIGATION' | 'SYSTEM';
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
context?: Record<string, any>;
|
|
7
|
+
runId?: string;
|
|
8
|
+
traceId?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function trackStateEvent(config: TellannBackendConfig, options: TrackStateOptions): Promise<void>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackStateEvent = trackStateEvent;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const MAX_EVENT_SIZE_BYTES = 32 * 1024; // 32 KB limit
|
|
6
|
+
async function trackStateEvent(config, options) {
|
|
7
|
+
const event = {
|
|
8
|
+
eventId: (0, uuid_1.v4)(),
|
|
9
|
+
sessionId: options.sessionId ?? config.sessionId ?? (0, uuid_1.v4)(),
|
|
10
|
+
tenantId: config.tenantId ?? 'unknown',
|
|
11
|
+
applicationId: config.applicationId,
|
|
12
|
+
environmentId: config.environmentId ?? null,
|
|
13
|
+
runId: options.runId ?? config.runId ?? null,
|
|
14
|
+
traceId: options.traceId ?? config.traceId ?? null,
|
|
15
|
+
agentVersion: config.agentVersion ?? null,
|
|
16
|
+
instrumentationManifestVersion: config.instrumentationManifestVersion ?? null,
|
|
17
|
+
source: 'backend-sdk',
|
|
18
|
+
eventVersion: '1.0',
|
|
19
|
+
eventType: 'STATE_ENTERED',
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
metadata: {
|
|
22
|
+
stateName: options.stateName,
|
|
23
|
+
category: options.category ?? 'BUSINESS',
|
|
24
|
+
context: options.context ?? {},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
// Enforce Size Limit
|
|
28
|
+
try {
|
|
29
|
+
const eventJson = JSON.stringify(event);
|
|
30
|
+
const eventSize = Buffer.byteLength(eventJson, 'utf8');
|
|
31
|
+
if (eventSize > MAX_EVENT_SIZE_BYTES) {
|
|
32
|
+
console.error(`[Tellann Backend] State event discarded. Size (${eventSize} bytes) exceeds limit of ${MAX_EVENT_SIZE_BYTES} bytes.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error('[Tellann Backend] Failed to compute size of state event, discarding', err);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
42
|
+
if (config.apiKey) {
|
|
43
|
+
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
44
|
+
}
|
|
45
|
+
if (config.environmentId) {
|
|
46
|
+
headers['x-tellann-environment-id'] = config.environmentId;
|
|
47
|
+
}
|
|
48
|
+
if (config.runId)
|
|
49
|
+
headers['x-tellann-run-id'] = config.runId;
|
|
50
|
+
if (config.traceId)
|
|
51
|
+
headers['x-tellann-trace-id'] = config.traceId;
|
|
52
|
+
await fetch(`${config.endpoint}/v1/events`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers,
|
|
55
|
+
body: JSON.stringify(event),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Silently swallow
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class BackendWorkflowTracker {
|
|
2
|
+
private workflows;
|
|
3
|
+
private readonly ttlMs;
|
|
4
|
+
private readonly maxLifetimeMs;
|
|
5
|
+
private cleanupInterval;
|
|
6
|
+
constructor();
|
|
7
|
+
start(name: string): string;
|
|
8
|
+
complete(workflowId: string): {
|
|
9
|
+
name: string;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
} | null;
|
|
12
|
+
fail(workflowId: string): {
|
|
13
|
+
name: string;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
} | null;
|
|
16
|
+
abandon(workflowId: string): void;
|
|
17
|
+
private cleanup;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BackendWorkflowTracker = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
class BackendWorkflowTracker {
|
|
6
|
+
workflows = new Map();
|
|
7
|
+
ttlMs = 30 * 60 * 1000; // 30 minutes idle timeout
|
|
8
|
+
maxLifetimeMs = 2 * 60 * 60 * 1000; // 2 hours absolute limit
|
|
9
|
+
cleanupInterval = null;
|
|
10
|
+
constructor() {
|
|
11
|
+
// Periodic cleanup every 5 minutes
|
|
12
|
+
if (typeof setInterval !== 'undefined') {
|
|
13
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);
|
|
14
|
+
// Allow Node process to exit if only this timer is active
|
|
15
|
+
if (this.cleanupInterval.unref) {
|
|
16
|
+
this.cleanupInterval.unref();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
start(name) {
|
|
21
|
+
this.cleanup(); // Clean up on start
|
|
22
|
+
const id = (0, uuid_1.v4)();
|
|
23
|
+
this.workflows.set(id, {
|
|
24
|
+
name,
|
|
25
|
+
startedAt: Date.now(),
|
|
26
|
+
lastAccessedAt: Date.now()
|
|
27
|
+
});
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
complete(workflowId) {
|
|
31
|
+
this.cleanup();
|
|
32
|
+
const workflow = this.workflows.get(workflowId);
|
|
33
|
+
if (!workflow)
|
|
34
|
+
return null;
|
|
35
|
+
this.workflows.delete(workflowId);
|
|
36
|
+
return {
|
|
37
|
+
name: workflow.name,
|
|
38
|
+
durationMs: Date.now() - workflow.startedAt
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
fail(workflowId) {
|
|
42
|
+
this.cleanup();
|
|
43
|
+
const workflow = this.workflows.get(workflowId);
|
|
44
|
+
if (!workflow)
|
|
45
|
+
return null;
|
|
46
|
+
this.workflows.delete(workflowId);
|
|
47
|
+
return {
|
|
48
|
+
name: workflow.name,
|
|
49
|
+
durationMs: Date.now() - workflow.startedAt
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
abandon(workflowId) {
|
|
53
|
+
this.workflows.delete(workflowId);
|
|
54
|
+
}
|
|
55
|
+
cleanup() {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
for (const [id, workflow] of this.workflows.entries()) {
|
|
58
|
+
const isIdleExpired = now - workflow.lastAccessedAt > this.ttlMs;
|
|
59
|
+
const isAbsoluteExpired = now - workflow.startedAt > this.maxLifetimeMs;
|
|
60
|
+
if (isIdleExpired || isAbsoluteExpired) {
|
|
61
|
+
this.workflows.delete(id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
destroy() {
|
|
66
|
+
if (this.cleanupInterval) {
|
|
67
|
+
clearInterval(this.cleanupInterval);
|
|
68
|
+
this.cleanupInterval = null;
|
|
69
|
+
}
|
|
70
|
+
this.workflows.clear();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.BackendWorkflowTracker = BackendWorkflowTracker;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type EventType = 'PAGE_VIEW' | 'ROUTE_CHANGE' | 'BUTTON_CLICK' | 'LINK_CLICK' | 'FORM_SUBMIT' | 'FORM_SUBMITTED' | 'API_REQUEST' | 'ERROR_EVENT' | 'ERROR_OCCURRED' | 'UNHANDLED_EXCEPTION' | 'SERVER_ERROR' | 'CLIENT_ERROR' | 'BUSINESS_EVENT' | 'STATE_ENTERED' | 'STATE_TRANSITION' | 'FLOW_INITIAL_STATE' | 'FLOW_STATE_REACHED' | 'FLOW_TRANSITION' | 'FLOW_TERMINAL_STATE' | 'WORKFLOW_STARTED' | 'WORKFLOW_COMPLETED' | 'WORKFLOW_FAILED' | 'WORKFLOW_CANCELLED' | 'TELLANN_ONBOARDING_TEST' | 'TELLANN_INITIALIZED' | 'QA_RUN_STARTED' | 'QA_RUN_COMPLETED' | 'QA_RUN_FAILED' | 'BROWSER_PAGE_LOADED' | 'BROWSER_CONSOLE_ERROR' | 'BROWSER_NETWORK_FAILED' | 'VISUAL_ASSERTION_FAILED' | 'ACCESSIBILITY_FINDING' | 'INSTRUMENTATION_VERIFIED' | 'REPOSITORY_SNAPSHOT_CREATED' | 'EXPECTED_FLOW_VERSION_SELECTED';
|
|
2
|
+
export interface TellannEvent {
|
|
3
|
+
eventId: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
tenantId: string;
|
|
6
|
+
applicationId: string;
|
|
7
|
+
environmentId?: string | null;
|
|
8
|
+
runId?: string | null;
|
|
9
|
+
traceId?: string | null;
|
|
10
|
+
agentVersion?: string | null;
|
|
11
|
+
instrumentationManifestVersion?: string | null;
|
|
12
|
+
source: string;
|
|
13
|
+
eventVersion: string;
|
|
14
|
+
eventType: EventType;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
metadata: Record<string, any>;
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TrackApiOptions } from './core/trackApi';
|
|
2
|
+
import { CaptureErrorOptions } from './core/captureError';
|
|
3
|
+
import { TrackStateOptions } from './core/trackState';
|
|
4
|
+
export * from './core/TELLANN';
|
|
5
|
+
export type { EventType, TellannEvent } from './event-types';
|
|
6
|
+
export { TrackApiOptions } from './core/trackApi';
|
|
7
|
+
export { CaptureErrorOptions } from './core/captureError';
|
|
8
|
+
export { TrackStateOptions } from './core/trackState';
|
|
9
|
+
export * from './integrations/express';
|
|
10
|
+
export * from './integrations/fastify';
|
|
11
|
+
/**
|
|
12
|
+
* Backward compatible helper to track an API call using the initialized TELLANN singleton.
|
|
13
|
+
*/
|
|
14
|
+
export declare function trackApi(options: TrackApiOptions): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Backward compatible helper to capture an error using the initialized TELLANN singleton.
|
|
17
|
+
*/
|
|
18
|
+
export declare function captureError(options: CaptureErrorOptions): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Backward compatible helper to track a state transition using the initialized TELLANN singleton.
|
|
21
|
+
*/
|
|
22
|
+
export declare function trackState(options: TrackStateOptions): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.trackApi = trackApi;
|
|
18
|
+
exports.captureError = captureError;
|
|
19
|
+
exports.trackState = trackState;
|
|
20
|
+
const TELLANN_1 = require("./core/TELLANN");
|
|
21
|
+
__exportStar(require("./core/TELLANN"), exports);
|
|
22
|
+
__exportStar(require("./integrations/express"), exports);
|
|
23
|
+
__exportStar(require("./integrations/fastify"), exports);
|
|
24
|
+
/**
|
|
25
|
+
* Backward compatible helper to track an API call using the initialized TELLANN singleton.
|
|
26
|
+
*/
|
|
27
|
+
async function trackApi(options) {
|
|
28
|
+
await TELLANN_1.TELLANN.trackApi(options);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Backward compatible helper to capture an error using the initialized TELLANN singleton.
|
|
32
|
+
*/
|
|
33
|
+
async function captureError(options) {
|
|
34
|
+
await TELLANN_1.TELLANN.captureError(options);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Backward compatible helper to track a state transition using the initialized TELLANN singleton.
|
|
38
|
+
*/
|
|
39
|
+
async function trackState(options) {
|
|
40
|
+
await TELLANN_1.TELLANN.trackState(options);
|
|
41
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ErrorRequestHandler, RequestHandler } from 'express';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Request {
|
|
5
|
+
tellann?: {
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
runId?: string;
|
|
8
|
+
traceId?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export declare function extractSessionId(headers: Record<string, any>): string | undefined;
|
|
14
|
+
export declare function extractCorrelationContext(headers: Record<string, any>): {
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
runId?: string;
|
|
17
|
+
traceId?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Express middleware that automatically tracks every API request and hydrates req.tellann context.
|
|
21
|
+
*
|
|
22
|
+
* The middleware reads the `X-TELLANN-Session-ID` or W3C `traceparent` header to correlate
|
|
23
|
+
* backend API calls with the originating frontend session.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tellannExpressMiddleware(): RequestHandler;
|
|
26
|
+
/**
|
|
27
|
+
* Global Express error-handling middleware that automatically captures unhandled errors.
|
|
28
|
+
*/
|
|
29
|
+
export declare function tellannExpressErrorHandler(): ErrorRequestHandler;
|
|
30
|
+
/** @deprecated Use tellannExpressMiddleware() */
|
|
31
|
+
export declare const expressMiddleware: typeof tellannExpressMiddleware;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expressMiddleware = void 0;
|
|
4
|
+
exports.extractSessionId = extractSessionId;
|
|
5
|
+
exports.extractCorrelationContext = extractCorrelationContext;
|
|
6
|
+
exports.tellannExpressMiddleware = tellannExpressMiddleware;
|
|
7
|
+
exports.tellannExpressErrorHandler = tellannExpressErrorHandler;
|
|
8
|
+
const TELLANN_1 = require("../../core/TELLANN");
|
|
9
|
+
function extractSessionId(headers) {
|
|
10
|
+
if (headers['x-tellann-session-id'] || headers['x-tellann-session-id']) {
|
|
11
|
+
return (headers['x-tellann-session-id'] || headers['x-tellann-session-id']);
|
|
12
|
+
}
|
|
13
|
+
const traceparent = headers['traceparent'];
|
|
14
|
+
if (traceparent) {
|
|
15
|
+
const parts = traceparent.split('-');
|
|
16
|
+
if (parts.length >= 2 && parts[1].length === 32) {
|
|
17
|
+
const t = parts[1];
|
|
18
|
+
return `${t.slice(0, 8)}-${t.slice(8, 12)}-${t.slice(12, 16)}-${t.slice(16, 20)}-${t.slice(20)}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function extractCorrelationContext(headers) {
|
|
24
|
+
const traceparent = headers.traceparent;
|
|
25
|
+
const traceId = headers['x-tellann-trace-id'] ?? traceparent?.split('-')[1];
|
|
26
|
+
return {
|
|
27
|
+
sessionId: extractSessionId(headers),
|
|
28
|
+
runId: headers['x-tellann-run-id'],
|
|
29
|
+
traceId,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Express middleware that automatically tracks every API request and hydrates req.tellann context.
|
|
34
|
+
*
|
|
35
|
+
* The middleware reads the `X-TELLANN-Session-ID` or W3C `traceparent` header to correlate
|
|
36
|
+
* backend API calls with the originating frontend session.
|
|
37
|
+
*/
|
|
38
|
+
function tellannExpressMiddleware() {
|
|
39
|
+
return (req, res, next) => {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const correlation = extractCorrelationContext(req.headers);
|
|
42
|
+
const { sessionId } = correlation;
|
|
43
|
+
const requestId = req.headers['x-request-id'];
|
|
44
|
+
// Decorate request object
|
|
45
|
+
req.tellann = correlation;
|
|
46
|
+
res.on('finish', () => {
|
|
47
|
+
TELLANN_1.TELLANN.trackApi({
|
|
48
|
+
endpoint: req.path,
|
|
49
|
+
method: req.method,
|
|
50
|
+
statusCode: res.statusCode,
|
|
51
|
+
durationMs: Date.now() - start,
|
|
52
|
+
sessionId,
|
|
53
|
+
requestId,
|
|
54
|
+
runId: correlation.runId,
|
|
55
|
+
traceId: correlation.traceId,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Global Express error-handling middleware that automatically captures unhandled errors.
|
|
63
|
+
*/
|
|
64
|
+
function tellannExpressErrorHandler() {
|
|
65
|
+
return (err, req, res, next) => {
|
|
66
|
+
const sessionId = req.tellann?.sessionId;
|
|
67
|
+
TELLANN_1.TELLANN.captureError({
|
|
68
|
+
error: err,
|
|
69
|
+
sessionId,
|
|
70
|
+
eventType: 'SERVER_ERROR',
|
|
71
|
+
runId: req.tellann?.runId,
|
|
72
|
+
traceId: req.tellann?.traceId,
|
|
73
|
+
context: {
|
|
74
|
+
path: req.path,
|
|
75
|
+
method: req.method,
|
|
76
|
+
query: req.query,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
next(err);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** @deprecated Use tellannExpressMiddleware() */
|
|
83
|
+
exports.expressMiddleware = tellannExpressMiddleware;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
declare module 'fastify' {
|
|
3
|
+
interface FastifyRequest {
|
|
4
|
+
tellann?: {
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
runId?: string;
|
|
7
|
+
traceId?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare const tellannFastifyPlugin: FastifyPluginAsync;
|
|
12
|
+
/** @deprecated Use tellannFastifyPlugin */
|
|
13
|
+
export declare const fastifyPlugin: FastifyPluginAsync;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fastifyPlugin = exports.tellannFastifyPlugin = void 0;
|
|
7
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
+
const TELLANN_1 = require("../../core/TELLANN");
|
|
9
|
+
const express_1 = require("../express");
|
|
10
|
+
/**
|
|
11
|
+
* Fastify plugin that automatically tracks every API request and handles error correlation.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* import { tellannFastifyPlugin } from '@tellann/backend-sdk';
|
|
15
|
+
* await fastify.register(tellannFastifyPlugin);
|
|
16
|
+
*
|
|
17
|
+
* The plugin reads the `x-tellann-session-id` or W3C `traceparent` header to correlate
|
|
18
|
+
* backend API calls with the originating frontend session.
|
|
19
|
+
*/
|
|
20
|
+
const tellannFastifyPluginImpl = async (fastify) => {
|
|
21
|
+
// Add preHandler to extract session metadata
|
|
22
|
+
fastify.addHook('onRequest', async (request) => {
|
|
23
|
+
request.tellann = (0, express_1.extractCorrelationContext)(request.headers);
|
|
24
|
+
});
|
|
25
|
+
// Track API completion
|
|
26
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
27
|
+
const sessionId = request.tellann?.sessionId;
|
|
28
|
+
const requestId = request.headers['x-request-id'];
|
|
29
|
+
await TELLANN_1.TELLANN.trackApi({
|
|
30
|
+
endpoint: request.routeOptions?.url ?? request.url,
|
|
31
|
+
method: request.method,
|
|
32
|
+
statusCode: reply.statusCode,
|
|
33
|
+
durationMs: Math.round(reply.elapsedTime),
|
|
34
|
+
sessionId,
|
|
35
|
+
requestId,
|
|
36
|
+
runId: request.tellann?.runId,
|
|
37
|
+
traceId: request.tellann?.traceId,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// Track errors
|
|
41
|
+
fastify.addHook('onError', async (request, reply, error) => {
|
|
42
|
+
const sessionId = request.tellann?.sessionId;
|
|
43
|
+
await TELLANN_1.TELLANN.captureError({
|
|
44
|
+
error,
|
|
45
|
+
sessionId,
|
|
46
|
+
eventType: 'SERVER_ERROR',
|
|
47
|
+
runId: request.tellann?.runId,
|
|
48
|
+
traceId: request.tellann?.traceId,
|
|
49
|
+
context: {
|
|
50
|
+
url: request.url,
|
|
51
|
+
method: request.method,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
exports.tellannFastifyPlugin = (0, fastify_plugin_1.default)(tellannFastifyPluginImpl, {
|
|
57
|
+
name: 'tellann-fastify-plugin',
|
|
58
|
+
fastify: '>=4.0.0',
|
|
59
|
+
});
|
|
60
|
+
/** @deprecated Use tellannFastifyPlugin */
|
|
61
|
+
exports.fastifyPlugin = exports.tellannFastifyPlugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tellann/backend-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tellann server telemetry and QA-run correlation SDK",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Pellumi/monitor.git",
|
|
11
|
+
"directory": "packages/backend-sdk"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/Pellumi/monitor/tree/main/packages/backend-sdk#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Pellumi/monitor/issues"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"!dist/**/*.test.js",
|
|
24
|
+
"!dist/**/*.test.d.ts"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"fastify-plugin": "^4.5.1",
|
|
31
|
+
"uuid": "^9.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/express": "^4.17.21",
|
|
35
|
+
"@types/uuid": "^9.0.8",
|
|
36
|
+
"express": "^4.18.2",
|
|
37
|
+
"fastify": "^4.26.0",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"@tellann/shared": "0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"express": ">=4",
|
|
43
|
+
"fastify": ">=4"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"express": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"fastify": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc",
|
|
55
|
+
"dev": "tsc -w",
|
|
56
|
+
"test": "node --test dist/index.test.js"
|
|
57
|
+
}
|
|
58
|
+
}
|