@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 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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }