@reactionary/source 0.0.28 → 0.0.30
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/core/package.json +2 -1
- package/core/src/providers/base.provider.ts +29 -9
- package/examples/trpc-node/.env.example +52 -0
- package/examples/trpc-node/src/main.ts +5 -1
- package/otel/README.md +247 -0
- package/otel/eslint.config.mjs +23 -0
- package/otel/package.json +12 -0
- package/otel/pnpm-lock.yaml +805 -0
- package/otel/project.json +33 -0
- package/otel/src/index.ts +9 -0
- package/otel/src/metrics.ts +75 -0
- package/otel/src/provider-instrumentation.ts +107 -0
- package/otel/src/sdk.ts +57 -0
- package/otel/src/tracer.ts +82 -0
- package/otel/src/trpc-middleware.ts +127 -0
- package/otel/tsconfig.json +23 -0
- package/otel/tsconfig.lib.json +23 -0
- package/otel/tsconfig.spec.json +28 -0
- package/otel/vite.config.ts +24 -0
- package/package.json +15 -1
- package/project.json +14 -0
- package/trpc/package.json +1 -0
- package/trpc/src/index.ts +9 -3
- package/tsconfig.base.json +1 -0
- package/vitest.workspace.ts +4 -0
- package/examples/vue/eslint.config.mjs +0 -24
- package/examples/vue/index.html +0 -13
- package/examples/vue/project.json +0 -8
- package/examples/vue/src/app/App.vue +0 -275
- package/examples/vue/src/main.ts +0 -6
- package/examples/vue/src/styles.scss +0 -9
- package/examples/vue/src/vue-shims.d.ts +0 -5
- package/examples/vue/tsconfig.app.json +0 -14
- package/examples/vue/tsconfig.json +0 -20
- package/examples/vue/vite.config.ts +0 -31
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "otel",
|
|
3
|
+
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "otel/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"release": {
|
|
7
|
+
"version": {
|
|
8
|
+
"manifestRootsToUpdate": ["dist/{projectRoot}"],
|
|
9
|
+
"currentVersionResolver": "git-tag",
|
|
10
|
+
"fallbackCurrentVersionResolver": "disk"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"tags": [],
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {
|
|
16
|
+
"executor": "@nx/esbuild:esbuild",
|
|
17
|
+
"outputs": ["{options.outputPath}"],
|
|
18
|
+
"options": {
|
|
19
|
+
"outputPath": "dist/otel",
|
|
20
|
+
"main": "otel/src/index.ts",
|
|
21
|
+
"tsConfig": "otel/tsconfig.lib.json",
|
|
22
|
+
"assets": ["otel/*.md"],
|
|
23
|
+
"format": ["cjs"],
|
|
24
|
+
"generatePackageJson": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"nx-release-publish": {
|
|
28
|
+
"options": {
|
|
29
|
+
"packageRoot": "dist/{projectRoot}"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// OpenTelemetry auto-initialization based on standard environment variables
|
|
2
|
+
// See: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
|
|
3
|
+
|
|
4
|
+
// Framework integration exports (internal use only)
|
|
5
|
+
export { createTRPCTracing } from './trpc-middleware';
|
|
6
|
+
export { createProviderInstrumentation } from './provider-instrumentation';
|
|
7
|
+
|
|
8
|
+
// Graceful shutdown for process termination
|
|
9
|
+
export { shutdownOtel } from './sdk';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { metrics, Meter, Counter, Histogram, UpDownCounter } from '@opentelemetry/api';
|
|
2
|
+
import { isOtelInitialized } from './sdk';
|
|
3
|
+
|
|
4
|
+
const METER_NAME = '@reactionary/otel';
|
|
5
|
+
const METER_VERSION = '0.0.1';
|
|
6
|
+
|
|
7
|
+
let globalMeter: Meter | null = null;
|
|
8
|
+
|
|
9
|
+
export function getMeter(): Meter {
|
|
10
|
+
if (!globalMeter) {
|
|
11
|
+
// Ensure OTEL is initialized before creating meter
|
|
12
|
+
isOtelInitialized();
|
|
13
|
+
globalMeter = metrics.getMeter(METER_NAME, METER_VERSION);
|
|
14
|
+
}
|
|
15
|
+
return globalMeter;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ReactMetrics {
|
|
19
|
+
requestCounter: Counter;
|
|
20
|
+
requestDuration: Histogram;
|
|
21
|
+
activeRequests: UpDownCounter;
|
|
22
|
+
errorCounter: Counter;
|
|
23
|
+
providerCallCounter: Counter;
|
|
24
|
+
providerCallDuration: Histogram;
|
|
25
|
+
cacheHitCounter: Counter;
|
|
26
|
+
cacheMissCounter: Counter;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let metricsInstance: ReactMetrics | null = null;
|
|
30
|
+
|
|
31
|
+
export function initializeMetrics(): ReactMetrics {
|
|
32
|
+
if (metricsInstance) {
|
|
33
|
+
return metricsInstance;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const meter = getMeter();
|
|
37
|
+
|
|
38
|
+
metricsInstance = {
|
|
39
|
+
requestCounter: meter.createCounter('reactionary.requests', {
|
|
40
|
+
description: 'Total number of requests',
|
|
41
|
+
}),
|
|
42
|
+
requestDuration: meter.createHistogram('reactionary.request.duration', {
|
|
43
|
+
description: 'Request duration in milliseconds',
|
|
44
|
+
unit: 'ms',
|
|
45
|
+
}),
|
|
46
|
+
activeRequests: meter.createUpDownCounter('reactionary.requests.active', {
|
|
47
|
+
description: 'Number of active requests',
|
|
48
|
+
}),
|
|
49
|
+
errorCounter: meter.createCounter('reactionary.errors', {
|
|
50
|
+
description: 'Total number of errors',
|
|
51
|
+
}),
|
|
52
|
+
providerCallCounter: meter.createCounter('reactionary.provider.calls', {
|
|
53
|
+
description: 'Total number of provider calls',
|
|
54
|
+
}),
|
|
55
|
+
providerCallDuration: meter.createHistogram('reactionary.provider.duration', {
|
|
56
|
+
description: 'Provider call duration in milliseconds',
|
|
57
|
+
unit: 'ms',
|
|
58
|
+
}),
|
|
59
|
+
cacheHitCounter: meter.createCounter('reactionary.cache.hits', {
|
|
60
|
+
description: 'Total number of cache hits',
|
|
61
|
+
}),
|
|
62
|
+
cacheMissCounter: meter.createCounter('reactionary.cache.misses', {
|
|
63
|
+
description: 'Total number of cache misses',
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return metricsInstance;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getMetrics(): ReactMetrics {
|
|
71
|
+
if (!metricsInstance) {
|
|
72
|
+
return initializeMetrics();
|
|
73
|
+
}
|
|
74
|
+
return metricsInstance;
|
|
75
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Span, SpanKind } from '@opentelemetry/api';
|
|
2
|
+
import { withSpan, setSpanAttributes } from './tracer';
|
|
3
|
+
import { getMetrics } from './metrics';
|
|
4
|
+
|
|
5
|
+
export interface ProviderSpanOptions {
|
|
6
|
+
providerName: string;
|
|
7
|
+
operationType: 'query' | 'mutation';
|
|
8
|
+
operationName?: string;
|
|
9
|
+
attributes?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function withProviderSpan<T>(
|
|
13
|
+
options: ProviderSpanOptions,
|
|
14
|
+
fn: (span: Span) => Promise<T>
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const { providerName, operationType, operationName, attributes = {} } = options;
|
|
17
|
+
const metrics = getMetrics();
|
|
18
|
+
const spanName = `provider.${providerName}.${operationType}${operationName ? `.${operationName}` : ''}`;
|
|
19
|
+
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
metrics.providerCallCounter.add(1, {
|
|
22
|
+
'provider.name': providerName,
|
|
23
|
+
'provider.operation.type': operationType,
|
|
24
|
+
'provider.operation.name': operationName || 'unknown',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return withSpan(
|
|
28
|
+
spanName,
|
|
29
|
+
async (span) => {
|
|
30
|
+
setSpanAttributes(span, {
|
|
31
|
+
'provider.name': providerName,
|
|
32
|
+
'provider.operation.type': operationType,
|
|
33
|
+
'provider.operation.name': operationName,
|
|
34
|
+
...attributes,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Span kind is set via options in withSpan
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await fn(span);
|
|
41
|
+
|
|
42
|
+
const duration = Date.now() - startTime;
|
|
43
|
+
metrics.providerCallDuration.record(duration, {
|
|
44
|
+
'provider.name': providerName,
|
|
45
|
+
'provider.operation.type': operationType,
|
|
46
|
+
'provider.operation.name': operationName || 'unknown',
|
|
47
|
+
'status': 'success',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const duration = Date.now() - startTime;
|
|
53
|
+
metrics.providerCallDuration.record(duration, {
|
|
54
|
+
'provider.name': providerName,
|
|
55
|
+
'provider.operation.type': operationType,
|
|
56
|
+
'provider.operation.name': operationName || 'unknown',
|
|
57
|
+
'status': 'error',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
metrics.errorCounter.add(1, {
|
|
61
|
+
'provider.name': providerName,
|
|
62
|
+
'provider.operation.type': operationType,
|
|
63
|
+
'provider.operation.name': operationName || 'unknown',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{ kind: SpanKind.CLIENT }
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createProviderInstrumentation(providerName: string) {
|
|
74
|
+
return {
|
|
75
|
+
traceQuery: <T>(
|
|
76
|
+
operationName: string,
|
|
77
|
+
fn: (span: Span) => Promise<T>,
|
|
78
|
+
attributes?: Record<string, unknown>
|
|
79
|
+
) => {
|
|
80
|
+
return withProviderSpan(
|
|
81
|
+
{
|
|
82
|
+
providerName,
|
|
83
|
+
operationType: 'query',
|
|
84
|
+
operationName,
|
|
85
|
+
attributes,
|
|
86
|
+
},
|
|
87
|
+
fn
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
traceMutation: <T>(
|
|
92
|
+
operationName: string,
|
|
93
|
+
fn: (span: Span) => Promise<T>,
|
|
94
|
+
attributes?: Record<string, unknown>
|
|
95
|
+
) => {
|
|
96
|
+
return withProviderSpan(
|
|
97
|
+
{
|
|
98
|
+
providerName,
|
|
99
|
+
operationType: 'mutation',
|
|
100
|
+
operationName,
|
|
101
|
+
attributes,
|
|
102
|
+
},
|
|
103
|
+
fn
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
package/otel/src/sdk.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
2
|
+
|
|
3
|
+
let sdk: NodeSDK | null = null;
|
|
4
|
+
let isInitialized = false;
|
|
5
|
+
let initializationPromise: Promise<void> | null = null;
|
|
6
|
+
|
|
7
|
+
// Detect if we're running in a browser environment
|
|
8
|
+
function isBrowser(): boolean {
|
|
9
|
+
return typeof window !== 'undefined' && typeof process === 'undefined';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Auto-initialize OTEL on first use with standard env vars
|
|
13
|
+
function ensureInitialized(): void {
|
|
14
|
+
if (isInitialized || initializationPromise) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Skip initialization in browser environments
|
|
19
|
+
if (isBrowser()) {
|
|
20
|
+
isInitialized = true;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Prevent multiple initialization attempts
|
|
25
|
+
initializationPromise = Promise.resolve().then(() => {
|
|
26
|
+
// Let NodeSDK handle everything automatically via env vars
|
|
27
|
+
// The SDK will automatically pick up OTEL_* environment variables
|
|
28
|
+
sdk = new NodeSDK();
|
|
29
|
+
|
|
30
|
+
sdk.start();
|
|
31
|
+
isInitialized = true;
|
|
32
|
+
|
|
33
|
+
process.on('SIGTERM', async () => {
|
|
34
|
+
try {
|
|
35
|
+
await shutdownOtel();
|
|
36
|
+
if (process.env['OTEL_LOG_LEVEL'] === 'debug') {
|
|
37
|
+
console.log('OpenTelemetry terminated successfully');
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error terminating OpenTelemetry', error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function shutdownOtel(): Promise<void> {
|
|
47
|
+
if (sdk) {
|
|
48
|
+
await sdk.shutdown();
|
|
49
|
+
sdk = null;
|
|
50
|
+
isInitialized = false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isOtelInitialized(): boolean {
|
|
55
|
+
ensureInitialized();
|
|
56
|
+
return isInitialized;
|
|
57
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
trace,
|
|
3
|
+
Tracer,
|
|
4
|
+
Span,
|
|
5
|
+
SpanStatusCode,
|
|
6
|
+
context as otelContext,
|
|
7
|
+
Context,
|
|
8
|
+
SpanOptions,
|
|
9
|
+
Attributes,
|
|
10
|
+
} from '@opentelemetry/api';
|
|
11
|
+
import { isOtelInitialized } from './sdk';
|
|
12
|
+
|
|
13
|
+
const TRACER_NAME = '@reactionary/otel';
|
|
14
|
+
const TRACER_VERSION = '0.0.1';
|
|
15
|
+
|
|
16
|
+
let globalTracer: Tracer | null = null;
|
|
17
|
+
|
|
18
|
+
export function getTracer(): Tracer {
|
|
19
|
+
if (!globalTracer) {
|
|
20
|
+
// Ensure OTEL is initialized before creating tracer
|
|
21
|
+
isOtelInitialized();
|
|
22
|
+
globalTracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
23
|
+
}
|
|
24
|
+
return globalTracer;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function startSpan(
|
|
28
|
+
name: string,
|
|
29
|
+
options?: SpanOptions,
|
|
30
|
+
context?: Context
|
|
31
|
+
): Span {
|
|
32
|
+
const tracer = getTracer();
|
|
33
|
+
if (context) {
|
|
34
|
+
return tracer.startActiveSpan(name, options || {}, context, (span) => span);
|
|
35
|
+
}
|
|
36
|
+
return tracer.startSpan(name, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function withSpan<T>(
|
|
40
|
+
name: string,
|
|
41
|
+
fn: (span: Span) => T | Promise<T>,
|
|
42
|
+
options?: SpanOptions
|
|
43
|
+
): Promise<T> {
|
|
44
|
+
const tracer = getTracer();
|
|
45
|
+
return tracer.startActiveSpan(name, options || {}, async (span) => {
|
|
46
|
+
try {
|
|
47
|
+
const result = await fn(span);
|
|
48
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
49
|
+
return result;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
span.setStatus({
|
|
52
|
+
code: SpanStatusCode.ERROR,
|
|
53
|
+
message: error instanceof Error ? error.message : String(error),
|
|
54
|
+
});
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
span.recordException(error);
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
} finally {
|
|
60
|
+
span.end();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function setSpanAttributes(span: Span, attributes: Attributes): void {
|
|
66
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
67
|
+
if (value !== undefined && value !== null) {
|
|
68
|
+
span.setAttribute(key, value);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createChildSpan(
|
|
74
|
+
parentSpan: Span,
|
|
75
|
+
name: string,
|
|
76
|
+
options?: SpanOptions
|
|
77
|
+
): Span {
|
|
78
|
+
const ctx = trace.setSpan(otelContext.active(), parentSpan);
|
|
79
|
+
return getTracer().startSpan(name, options, ctx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
|
2
|
+
import {
|
|
3
|
+
Span,
|
|
4
|
+
SpanKind,
|
|
5
|
+
SpanStatusCode,
|
|
6
|
+
} from '@opentelemetry/api';
|
|
7
|
+
import { getTracer } from './tracer';
|
|
8
|
+
import { getMetrics } from './metrics';
|
|
9
|
+
|
|
10
|
+
export interface TRPCMiddlewareOptions {
|
|
11
|
+
/** Whether to include input data in span attributes */
|
|
12
|
+
includeInput?: boolean;
|
|
13
|
+
/** Whether to include output data in span attributes */
|
|
14
|
+
includeOutput?: boolean;
|
|
15
|
+
/** Maximum string length for attributes */
|
|
16
|
+
maxAttributeLength?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const defaultOptions: TRPCMiddlewareOptions = {
|
|
20
|
+
includeInput: true,
|
|
21
|
+
includeOutput: false,
|
|
22
|
+
maxAttributeLength: 1000,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function createTRPCTracing(options: TRPCMiddlewareOptions = {}) {
|
|
26
|
+
const opts = { ...defaultOptions, ...options };
|
|
27
|
+
const metrics = getMetrics();
|
|
28
|
+
|
|
29
|
+
return ({ path, type, next, input, rawInput }: any) => {
|
|
30
|
+
const pathStr = path || 'unknown';
|
|
31
|
+
const tracer = getTracer();
|
|
32
|
+
const spanName = `trpc.${type}.${pathStr}`;
|
|
33
|
+
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
metrics.requestCounter.add(1, {
|
|
36
|
+
'rpc.method': pathStr,
|
|
37
|
+
'rpc.system': 'trpc',
|
|
38
|
+
'rpc.service': type,
|
|
39
|
+
});
|
|
40
|
+
metrics.activeRequests.add(1);
|
|
41
|
+
|
|
42
|
+
return tracer.startActiveSpan(
|
|
43
|
+
spanName,
|
|
44
|
+
{
|
|
45
|
+
kind: type === 'mutation' ? SpanKind.CLIENT : SpanKind.SERVER,
|
|
46
|
+
attributes: {
|
|
47
|
+
'rpc.system': 'trpc',
|
|
48
|
+
'rpc.method': pathStr,
|
|
49
|
+
'rpc.service': type,
|
|
50
|
+
'trpc.type': type,
|
|
51
|
+
'trpc.path': pathStr,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
async (span: Span) => {
|
|
55
|
+
try {
|
|
56
|
+
if (opts.includeInput && (rawInput !== undefined || input !== undefined)) {
|
|
57
|
+
const inputData = rawInput || input;
|
|
58
|
+
const inputStr = truncateString(JSON.stringify(inputData), opts.maxAttributeLength || 1000);
|
|
59
|
+
span.setAttribute('trpc.input', inputStr);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = await next();
|
|
63
|
+
|
|
64
|
+
if (opts.includeOutput && result !== undefined) {
|
|
65
|
+
const outputStr = truncateString(JSON.stringify(result), opts.maxAttributeLength || 1000);
|
|
66
|
+
span.setAttribute('trpc.output', outputStr);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
70
|
+
|
|
71
|
+
const duration = Date.now() - startTime;
|
|
72
|
+
metrics.requestDuration.record(duration, {
|
|
73
|
+
'rpc.method': pathStr,
|
|
74
|
+
'rpc.system': 'trpc',
|
|
75
|
+
'rpc.service': type,
|
|
76
|
+
'status': 'success',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
82
|
+
const errorCode = error instanceof TRPCError ? error.code : 'INTERNAL_SERVER_ERROR';
|
|
83
|
+
|
|
84
|
+
span.setStatus({
|
|
85
|
+
code: SpanStatusCode.ERROR,
|
|
86
|
+
message: errorMessage,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
span.setAttribute('trpc.error.code', errorCode);
|
|
90
|
+
span.setAttribute('trpc.error.message', errorMessage);
|
|
91
|
+
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
span.recordException(error);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const duration = Date.now() - startTime;
|
|
97
|
+
metrics.requestDuration.record(duration, {
|
|
98
|
+
'rpc.method': pathStr,
|
|
99
|
+
'rpc.system': 'trpc',
|
|
100
|
+
'rpc.service': type,
|
|
101
|
+
'status': 'error',
|
|
102
|
+
'error.code': errorCode,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
metrics.errorCounter.add(1, {
|
|
106
|
+
'rpc.method': pathStr,
|
|
107
|
+
'rpc.system': 'trpc',
|
|
108
|
+
'rpc.service': type,
|
|
109
|
+
'error.code': errorCode,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
throw error;
|
|
113
|
+
} finally {
|
|
114
|
+
span.end();
|
|
115
|
+
metrics.activeRequests.add(-1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function truncateString(str: string, maxLength: number): string {
|
|
123
|
+
if (str.length <= maxLength) {
|
|
124
|
+
return str;
|
|
125
|
+
}
|
|
126
|
+
return str.substring(0, maxLength) + '...';
|
|
127
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"noImplicitOverride": true,
|
|
9
|
+
"noImplicitReturns": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true,
|
|
11
|
+
"noPropertyAccessFromIndexSignature": true
|
|
12
|
+
},
|
|
13
|
+
"files": [],
|
|
14
|
+
"include": [],
|
|
15
|
+
"references": [
|
|
16
|
+
{
|
|
17
|
+
"path": "./tsconfig.lib.json"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "./tsconfig.spec.json"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"types": ["node"]
|
|
7
|
+
},
|
|
8
|
+
"include": ["src/**/*.ts"],
|
|
9
|
+
"exclude": [
|
|
10
|
+
"vite.config.ts",
|
|
11
|
+
"vite.config.mts",
|
|
12
|
+
"vitest.config.ts",
|
|
13
|
+
"vitest.config.mts",
|
|
14
|
+
"src/**/*.test.ts",
|
|
15
|
+
"src/**/*.spec.ts",
|
|
16
|
+
"src/**/*.test.tsx",
|
|
17
|
+
"src/**/*.spec.tsx",
|
|
18
|
+
"src/**/*.test.js",
|
|
19
|
+
"src/**/*.spec.js",
|
|
20
|
+
"src/**/*.test.jsx",
|
|
21
|
+
"src/**/*.spec.jsx"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../dist/out-tsc",
|
|
5
|
+
"types": [
|
|
6
|
+
"vitest/globals",
|
|
7
|
+
"vitest/importMeta",
|
|
8
|
+
"vite/client",
|
|
9
|
+
"node",
|
|
10
|
+
"vitest"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"vite.config.ts",
|
|
15
|
+
"vite.config.mts",
|
|
16
|
+
"vitest.config.ts",
|
|
17
|
+
"vitest.config.mts",
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/**/*.spec.ts",
|
|
20
|
+
"src/**/*.test.tsx",
|
|
21
|
+
"src/**/*.spec.tsx",
|
|
22
|
+
"src/**/*.test.js",
|
|
23
|
+
"src/**/*.spec.js",
|
|
24
|
+
"src/**/*.test.jsx",
|
|
25
|
+
"src/**/*.spec.jsx",
|
|
26
|
+
"src/**/*.d.ts"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
|
3
|
+
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
|
4
|
+
|
|
5
|
+
export default defineConfig(() => ({
|
|
6
|
+
root: __dirname,
|
|
7
|
+
cacheDir: '../node_modules/.vite/otel',
|
|
8
|
+
plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
|
9
|
+
// Uncomment this if you are using workers.
|
|
10
|
+
// worker: {
|
|
11
|
+
// plugins: [ nxViteTsPaths() ],
|
|
12
|
+
// },
|
|
13
|
+
test: {
|
|
14
|
+
watch: false,
|
|
15
|
+
globals: true,
|
|
16
|
+
environment: 'node',
|
|
17
|
+
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
|
18
|
+
reporters: ['default'],
|
|
19
|
+
coverage: {
|
|
20
|
+
reportsDirectory: '../coverage/otel',
|
|
21
|
+
provider: 'v8' as const,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/source",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
@@ -14,6 +14,19 @@
|
|
|
14
14
|
"@commercetools/platform-sdk": "^8.8.0",
|
|
15
15
|
"@commercetools/ts-client": "^3.2.2",
|
|
16
16
|
"@faker-js/faker": "^9.8.0",
|
|
17
|
+
"@opentelemetry/api": "^1.9.0",
|
|
18
|
+
"@opentelemetry/core": "^2.0.1",
|
|
19
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.203.0",
|
|
20
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
|
|
21
|
+
"@opentelemetry/instrumentation": "^0.203.0",
|
|
22
|
+
"@opentelemetry/instrumentation-express": "^0.52.0",
|
|
23
|
+
"@opentelemetry/instrumentation-http": "^0.203.0",
|
|
24
|
+
"@opentelemetry/resources": "^2.0.1",
|
|
25
|
+
"@opentelemetry/sdk-metrics": "^2.0.1",
|
|
26
|
+
"@opentelemetry/sdk-node": "^0.203.0",
|
|
27
|
+
"@opentelemetry/sdk-trace-base": "^2.0.1",
|
|
28
|
+
"@opentelemetry/sdk-trace-node": "^2.0.1",
|
|
29
|
+
"@opentelemetry/semantic-conventions": "^1.36.0",
|
|
17
30
|
"@trpc/client": "^11.1.2",
|
|
18
31
|
"@trpc/server": "^11.1.2",
|
|
19
32
|
"@upstash/redis": "^1.34.9",
|
|
@@ -71,6 +84,7 @@
|
|
|
71
84
|
"@types/react-dom": "19.0.0",
|
|
72
85
|
"@typescript-eslint/utils": "^8.19.0",
|
|
73
86
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
87
|
+
"@vitest/coverage-v8": "^3.0.5",
|
|
74
88
|
"@vitest/ui": "^3.0.0",
|
|
75
89
|
"@vue/eslint-config-prettier": "7.1.0",
|
|
76
90
|
"@vue/eslint-config-typescript": "^11.0.3",
|
package/project.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reactionary/source",
|
|
3
|
+
"$schema": "node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"targets": {
|
|
5
|
+
"local-registry": {
|
|
6
|
+
"executor": "@nx/js:verdaccio",
|
|
7
|
+
"options": {
|
|
8
|
+
"port": 4873,
|
|
9
|
+
"config": ".verdaccio/config.yml",
|
|
10
|
+
"storage": "tmp/local-registry/storage"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|