@reactionary/source 0.0.38 → 0.0.39
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 +1 -2
- package/core/src/index.ts +0 -2
- package/examples/next/.swcrc +30 -0
- package/examples/next/eslint.config.mjs +21 -0
- package/examples/next/index.d.ts +6 -0
- package/examples/next/next-env.d.ts +5 -0
- package/examples/next/next.config.js +20 -0
- package/examples/next/project.json +9 -0
- package/examples/next/public/.gitkeep +0 -0
- package/examples/next/public/favicon.ico +0 -0
- package/examples/next/src/app/global.css +0 -0
- package/examples/next/src/app/layout.tsx +18 -0
- package/examples/next/src/app/page.module.scss +2 -0
- package/examples/next/src/app/page.tsx +51 -0
- package/examples/next/src/instrumentation.ts +9 -0
- package/examples/next/tsconfig.json +44 -0
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
- package/otel/README.md +152 -172
- package/otel/package.json +0 -1
- package/otel/src/index.ts +16 -6
- package/otel/src/metrics.ts +3 -3
- package/otel/src/trace-decorator.ts +76 -97
- package/otel/src/tracer.ts +3 -3
- package/package.json +1 -1
- package/providers/fake/src/providers/cart.provider.ts +1 -1
- package/providers/fake/src/providers/search.provider.ts +11 -2
- package/trpc/package.json +1 -2
- package/trpc/src/client.ts +1 -3
- package/tsconfig.base.json +2 -0
- package/core/src/decorators/trpc.decorators.ts +0 -144
- package/otel/src/sdk.ts +0 -57
package/core/package.json
CHANGED
package/core/src/index.ts
CHANGED
|
@@ -6,8 +6,6 @@ export * from './cache/noop-cache';
|
|
|
6
6
|
export * from './client/client';
|
|
7
7
|
export * from './client/client-builder';
|
|
8
8
|
|
|
9
|
-
export * from './decorators/trpc.decorators';
|
|
10
|
-
|
|
11
9
|
export * from './providers/analytics.provider';
|
|
12
10
|
export * from './providers/base.provider';
|
|
13
11
|
export * from './providers/cart.provider';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsc": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"parser": {
|
|
5
|
+
"syntax": "typescript",
|
|
6
|
+
"decorators": true,
|
|
7
|
+
"dynamicImport": true
|
|
8
|
+
},
|
|
9
|
+
"transform": {
|
|
10
|
+
"decoratorMetadata": true,
|
|
11
|
+
"legacyDecorator": true
|
|
12
|
+
},
|
|
13
|
+
"keepClassNames": true,
|
|
14
|
+
"externalHelpers": true,
|
|
15
|
+
"loose": true
|
|
16
|
+
},
|
|
17
|
+
"module": {
|
|
18
|
+
"type": "commonjs"
|
|
19
|
+
},
|
|
20
|
+
"sourceMaps": true,
|
|
21
|
+
"exclude": [
|
|
22
|
+
"jest.config.ts",
|
|
23
|
+
".*\\.spec.tsx?$",
|
|
24
|
+
".*\\.test.tsx?$",
|
|
25
|
+
"./src/jest-setup.ts$",
|
|
26
|
+
"./**/jest-setup.ts$",
|
|
27
|
+
".*.js$",
|
|
28
|
+
".*.d.ts$"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FlatCompat } from '@eslint/eslintrc';
|
|
2
|
+
import { dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import js from '@eslint/js';
|
|
5
|
+
import { fixupConfigRules } from '@eslint/compat';
|
|
6
|
+
import nx from '@nx/eslint-plugin';
|
|
7
|
+
import baseConfig from '../../eslint.config.mjs';
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: dirname(fileURLToPath(import.meta.url)),
|
|
10
|
+
recommendedConfig: js.configs.recommended,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default [
|
|
14
|
+
...fixupConfigRules(compat.extends('next')),
|
|
15
|
+
...fixupConfigRules(compat.extends('next/core-web-vitals')),
|
|
16
|
+
...baseConfig,
|
|
17
|
+
...nx.configs['flat/react-typescript'],
|
|
18
|
+
{
|
|
19
|
+
ignores: ['.next/**/*'],
|
|
20
|
+
},
|
|
21
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const { composePlugins, withNx } = require('@nx/next');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
|
8
|
+
**/
|
|
9
|
+
const nextConfig = {
|
|
10
|
+
// Use this to set Nx-specific options
|
|
11
|
+
// See: https://nx.dev/recipes/next/next-config-setup
|
|
12
|
+
nx: {},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const plugins = [
|
|
16
|
+
// Add more Next.js plugins to this list if needed.
|
|
17
|
+
withNx,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
module.exports = composePlugins(...plugins)(nextConfig);
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import './global.css';
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: 'Welcome to next',
|
|
5
|
+
description: 'Generated by create-nx-workspace',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function RootLayout({
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<body>{children}</body>
|
|
16
|
+
</html>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import styles from './page.module.scss';
|
|
2
|
+
import { ClientBuilder, NoOpCache, SessionSchema } from '@reactionary/core';
|
|
3
|
+
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
4
|
+
|
|
5
|
+
export default async function Index() {
|
|
6
|
+
const client = new ClientBuilder()
|
|
7
|
+
.withCapability(
|
|
8
|
+
withFakeCapabilities(
|
|
9
|
+
{
|
|
10
|
+
jitter: {
|
|
11
|
+
mean: 0,
|
|
12
|
+
deviation: 0,
|
|
13
|
+
},
|
|
14
|
+
seeds: {
|
|
15
|
+
product: 1,
|
|
16
|
+
search: 1,
|
|
17
|
+
category: 1,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{ search: true, product: false, identity: false }
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
.withCache(new NoOpCache())
|
|
24
|
+
.build();
|
|
25
|
+
|
|
26
|
+
const session = SessionSchema.parse({
|
|
27
|
+
id: '1234567890',
|
|
28
|
+
languageContext: {
|
|
29
|
+
countryCode: 'US',
|
|
30
|
+
languageCode: 'en',
|
|
31
|
+
currencyCode: 'USD',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const search = await client.search?.queryByTerm({
|
|
36
|
+
search: {
|
|
37
|
+
facets: [],
|
|
38
|
+
page: 0,
|
|
39
|
+
pageSize: 12,
|
|
40
|
+
term: 'glass',
|
|
41
|
+
},
|
|
42
|
+
}, session);
|
|
43
|
+
|
|
44
|
+
return <div className={styles.page}>
|
|
45
|
+
{search?.products.map((product, index) => (
|
|
46
|
+
<div key={index}>
|
|
47
|
+
{ product.name }
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "preserve",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
"emitDeclarationOnly": false,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"module": "esnext",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"lib": [
|
|
14
|
+
"dom",
|
|
15
|
+
"dom.iterable",
|
|
16
|
+
"esnext"
|
|
17
|
+
],
|
|
18
|
+
"allowJs": true,
|
|
19
|
+
"allowSyntheticDefaultImports": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"incremental": true,
|
|
22
|
+
"plugins": [
|
|
23
|
+
{
|
|
24
|
+
"name": "next"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"**/*.js",
|
|
30
|
+
"**/*.jsx",
|
|
31
|
+
"**/*.ts",
|
|
32
|
+
"**/*.tsx",
|
|
33
|
+
"../../dist/examples/next/.next/types/**/*.ts",
|
|
34
|
+
"../../examples/next/.next/types/**/*.ts",
|
|
35
|
+
"next-env.d.ts",
|
|
36
|
+
".next/types/**/*.ts"
|
|
37
|
+
],
|
|
38
|
+
"exclude": [
|
|
39
|
+
"node_modules",
|
|
40
|
+
"jest.config.ts",
|
|
41
|
+
"**/*.spec.ts",
|
|
42
|
+
"**/*.test.ts"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -68,7 +68,6 @@ describe('basic node provider extension (models)', () => {
|
|
|
68
68
|
it('should get the enabled set of capabilities across providers', async () => {
|
|
69
69
|
expect(client.product).toBeDefined();
|
|
70
70
|
expect(client.search).toBeDefined();
|
|
71
|
-
expect(client.identity).toBeUndefined();
|
|
72
71
|
});
|
|
73
72
|
|
|
74
73
|
it('should be able to call the regular methods and get the default value', async () => {
|
|
@@ -26,7 +26,6 @@ describe('basic node setup', () => {
|
|
|
26
26
|
it('should only get back the enabled capabilities', async () => {
|
|
27
27
|
expect(client.product).toBeDefined();
|
|
28
28
|
expect(client.search).toBeDefined();
|
|
29
|
-
expect(client.identity).toBeUndefined();
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
it('should be able to call the enabled capabilities', async () => {
|
package/otel/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# @reactionary/otel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
OpenTelemetry instrumentation for the Reactionary framework. Provides decorators and utilities for tracing function execution and performance monitoring.
|
|
4
|
+
|
|
5
|
+
## Important: SDK Initialization Required
|
|
6
|
+
|
|
7
|
+
This library provides **instrumentation only**. The host application is responsible for initializing the OpenTelemetry SDK. Without proper SDK initialization, traces will be created as `NonRecordingSpan` instances with zero trace/span IDs.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Metrics Collection**: Request counts, durations, errors automatically tracked
|
|
13
|
-
- **Lazy Initialization**: Only starts when actually used
|
|
11
|
+
- **Tracing Decorators**: Automatic span creation for decorated methods
|
|
12
|
+
- **Manual Instrumentation**: Utilities for custom tracing
|
|
13
|
+
- **Framework Integration**: Built-in support for tRPC and providers
|
|
14
|
+
- **Zero Dependencies**: Only requires OpenTelemetry API
|
|
15
|
+
- **Graceful Degradation**: Works without SDK initialization (produces no-op spans)
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
@@ -18,230 +20,208 @@ Zero-configuration OpenTelemetry instrumentation for Reactionary framework. Auto
|
|
|
18
20
|
pnpm add @reactionary/otel
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
**Important**: You must also install and configure the OpenTelemetry SDK in your host application.
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## Usage
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
### Basic Tracing with Decorators
|
|
26
28
|
|
|
27
29
|
```typescript
|
|
28
|
-
|
|
29
|
-
// Just use your tRPC router or providers normally
|
|
30
|
-
import { createTRPCRouter } from '@reactionary/trpc';
|
|
31
|
-
|
|
32
|
-
const router = createTRPCRouter(client);
|
|
33
|
-
// ↑ Automatically instrumented when OTEL env vars are set
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Configuration
|
|
37
|
-
|
|
38
|
-
Use standard OpenTelemetry environment variables. No code changes needed.
|
|
39
|
-
|
|
40
|
-
### Standard Environment Variables
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# Service identification
|
|
44
|
-
OTEL_SERVICE_NAME=my-service
|
|
45
|
-
OTEL_SERVICE_VERSION=1.0.0
|
|
46
|
-
|
|
47
|
-
# Traces exporter (console | otlp | otlp/http | none)
|
|
48
|
-
OTEL_TRACES_EXPORTER=otlp
|
|
49
|
-
|
|
50
|
-
# Metrics exporter (console | otlp | otlp/http | none)
|
|
51
|
-
OTEL_METRICS_EXPORTER=otlp
|
|
52
|
-
|
|
53
|
-
# OTLP endpoint and headers
|
|
54
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
55
|
-
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
56
|
-
|
|
57
|
-
# Or use specific endpoints
|
|
58
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.honeycomb.io/v1/traces
|
|
59
|
-
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.honeycomb.io/v1/metrics
|
|
60
|
-
|
|
61
|
-
# Debug logging
|
|
62
|
-
OTEL_LOG_LEVEL=debug
|
|
63
|
-
|
|
64
|
-
# Metrics export interval (milliseconds)
|
|
65
|
-
OTEL_METRIC_EXPORT_INTERVAL=60000
|
|
66
|
-
```
|
|
30
|
+
import { traced } from '@reactionary/otel';
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
OTEL_TRACES_EXPORTER=console
|
|
75
|
-
OTEL_METRICS_EXPORTER=console
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### OTLP (Production)
|
|
79
|
-
Works with any OTLP-compatible backend:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
OTEL_TRACES_EXPORTER=otlp
|
|
83
|
-
OTEL_METRICS_EXPORTER=otlp
|
|
84
|
-
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
85
|
-
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
86
|
-
```
|
|
32
|
+
class MyService {
|
|
33
|
+
@traced()
|
|
34
|
+
async fetchData(id: string): Promise<Data> {
|
|
35
|
+
// This method will be automatically traced
|
|
36
|
+
return await dataSource.get(id);
|
|
37
|
+
}
|
|
87
38
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
39
|
+
@traced({
|
|
40
|
+
spanName: 'custom-operation',
|
|
41
|
+
captureResult: false
|
|
42
|
+
})
|
|
43
|
+
processData(data: Data): void {
|
|
44
|
+
// Custom span name and no result capture
|
|
45
|
+
}
|
|
46
|
+
}
|
|
92
47
|
```
|
|
93
48
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### Manual Spans
|
|
97
|
-
|
|
98
|
-
Create custom spans for specific operations:
|
|
49
|
+
### Manual Instrumentation
|
|
99
50
|
|
|
100
51
|
```typescript
|
|
101
52
|
import { withSpan, getTracer } from '@reactionary/otel';
|
|
102
53
|
|
|
103
54
|
// Using withSpan helper
|
|
104
|
-
const result = await withSpan('
|
|
105
|
-
span.setAttribute('
|
|
106
|
-
|
|
107
|
-
return someAsyncOperation();
|
|
55
|
+
const result = await withSpan('my-operation', async (span) => {
|
|
56
|
+
span.setAttribute('operation.id', operationId);
|
|
57
|
+
return await performOperation();
|
|
108
58
|
});
|
|
109
59
|
|
|
110
60
|
// Using tracer directly
|
|
111
61
|
const tracer = getTracer();
|
|
112
62
|
const span = tracer.startSpan('manual-span');
|
|
113
63
|
try {
|
|
114
|
-
// Your
|
|
64
|
+
// Your code here
|
|
115
65
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
116
66
|
} catch (error) {
|
|
117
|
-
span.recordException(error);
|
|
118
67
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
119
|
-
|
|
68
|
+
span.recordException(error);
|
|
120
69
|
} finally {
|
|
121
70
|
span.end();
|
|
122
71
|
}
|
|
123
72
|
```
|
|
124
73
|
|
|
125
|
-
|
|
74
|
+
## Setting Up OpenTelemetry SDK
|
|
126
75
|
|
|
127
|
-
|
|
76
|
+
### Next.js Applications
|
|
128
77
|
|
|
129
|
-
|
|
130
|
-
import { BaseProvider } from '@reactionary/core';
|
|
78
|
+
1. Create an `instrumentation.ts` file in your project root:
|
|
131
79
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
80
|
+
```typescript
|
|
81
|
+
// instrumentation.ts
|
|
82
|
+
export async function register() {
|
|
83
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
84
|
+
const { NodeSDK } = await import('@opentelemetry/sdk-node');
|
|
85
|
+
const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node');
|
|
86
|
+
|
|
87
|
+
const sdk = new NodeSDK({
|
|
88
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
sdk.start();
|
|
140
92
|
}
|
|
141
93
|
}
|
|
142
94
|
```
|
|
143
95
|
|
|
144
|
-
|
|
96
|
+
2. Enable instrumentation in `next.config.js`:
|
|
145
97
|
|
|
146
|
-
|
|
98
|
+
```javascript
|
|
99
|
+
/** @type {import('next').NextConfig} */
|
|
100
|
+
const nextConfig = {
|
|
101
|
+
experimental: {
|
|
102
|
+
instrumentationHook: true,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
147
105
|
|
|
148
|
-
|
|
149
|
-
|
|
106
|
+
module.exports = nextConfig;
|
|
107
|
+
```
|
|
150
108
|
|
|
151
|
-
|
|
109
|
+
3. Configure environment variables:
|
|
152
110
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
111
|
+
```bash
|
|
112
|
+
# .env.local
|
|
113
|
+
OTEL_SERVICE_NAME=my-nextjs-app
|
|
114
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
115
|
+
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Node.js Applications
|
|
158
119
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
120
|
+
```typescript
|
|
121
|
+
// Initialize at the very beginning of your application
|
|
122
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
123
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
124
|
+
|
|
125
|
+
const sdk = new NodeSDK({
|
|
126
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
163
127
|
});
|
|
128
|
+
|
|
129
|
+
sdk.start();
|
|
130
|
+
|
|
131
|
+
// Now import and use your application code
|
|
132
|
+
import './app';
|
|
164
133
|
```
|
|
165
134
|
|
|
166
|
-
|
|
135
|
+
### Environment Variables
|
|
167
136
|
|
|
168
|
-
|
|
137
|
+
The OpenTelemetry SDK can be configured using environment variables:
|
|
169
138
|
|
|
170
139
|
```bash
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
175
|
-
```
|
|
140
|
+
# Service identification
|
|
141
|
+
OTEL_SERVICE_NAME=my-app
|
|
142
|
+
OTEL_SERVICE_VERSION=1.0.0
|
|
176
143
|
|
|
177
|
-
|
|
144
|
+
# OTLP Exporter (for Jaeger, etc.)
|
|
145
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
146
|
+
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
|
178
147
|
|
|
179
|
-
|
|
180
|
-
OTEL_SERVICE_NAME=my-service-dev
|
|
148
|
+
# Console exporter (for development)
|
|
181
149
|
OTEL_TRACES_EXPORTER=console
|
|
182
|
-
|
|
150
|
+
|
|
151
|
+
# Sampling (optional)
|
|
152
|
+
OTEL_TRACES_SAMPLER=always_on
|
|
153
|
+
|
|
154
|
+
# Debug logging
|
|
155
|
+
OTEL_LOG_LEVEL=debug
|
|
183
156
|
```
|
|
184
157
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
- OTEL_SERVICE_NAME=my-service
|
|
194
|
-
|
|
195
|
-
jaeger:
|
|
196
|
-
image: jaegertracing/all-in-one:latest
|
|
197
|
-
ports:
|
|
198
|
-
- "16686:16686" # Jaeger UI
|
|
199
|
-
- "4318:4318" # OTLP HTTP
|
|
158
|
+
## Troubleshooting
|
|
159
|
+
|
|
160
|
+
### ProxyTracer with NonRecordingSpan
|
|
161
|
+
|
|
162
|
+
If you see logs like:
|
|
163
|
+
```
|
|
164
|
+
tracer: ProxyTracer { _provider: ProxyTracerProvider {} }
|
|
165
|
+
ending span: NonRecordingSpan { _spanContext: { traceId: '00000000000000000000000000000000' } }
|
|
200
166
|
```
|
|
201
167
|
|
|
202
|
-
|
|
168
|
+
This means the OpenTelemetry SDK has not been initialized. Ensure you have:
|
|
169
|
+
1. Created an `instrumentation.ts` file (Next.js)
|
|
170
|
+
2. Initialized the SDK at application startup (Node.js)
|
|
171
|
+
3. Set the required environment variables
|
|
203
172
|
|
|
204
|
-
|
|
173
|
+
### Missing Traces
|
|
205
174
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
| `reactionary.requests.active` | UpDownCounter | Number of active requests |
|
|
211
|
-
| `reactionary.errors` | Counter | Total number of errors |
|
|
212
|
-
| `reactionary.provider.calls` | Counter | Total provider calls |
|
|
213
|
-
| `reactionary.provider.duration` | Histogram | Provider call duration |
|
|
214
|
-
| `reactionary.cache.hits` | Counter | Cache hit count |
|
|
215
|
-
| `reactionary.cache.misses` | Counter | Cache miss count |
|
|
175
|
+
If the decorator is being applied but you don't see traces:
|
|
176
|
+
1. Verify the OTEL exporter is configured correctly
|
|
177
|
+
2. Check that your tracing backend is running
|
|
178
|
+
3. Ensure sampling is enabled (`OTEL_TRACES_SAMPLER=always_on`)
|
|
216
179
|
|
|
217
|
-
##
|
|
180
|
+
## API Reference
|
|
218
181
|
|
|
219
|
-
|
|
220
|
-
2. **Set Service Name**: Always set `OTEL_SERVICE_NAME` for service identification
|
|
221
|
-
3. **Environment-based Config**: Use different configs for dev/staging/production
|
|
222
|
-
4. **Add Context**: Use span attributes to add business context to traces
|
|
223
|
-
5. **Handle Errors**: Ensure spans are properly closed even on errors
|
|
224
|
-
6. **Sample Wisely**: Consider sampling strategies for high-volume services
|
|
225
|
-
7. **Monitor Performance**: Watch for overhead in high-throughput scenarios
|
|
182
|
+
### Decorators
|
|
226
183
|
|
|
227
|
-
|
|
184
|
+
#### `@traced(options?)`
|
|
228
185
|
|
|
229
|
-
|
|
186
|
+
Decorates a method to automatically create spans for its execution.
|
|
230
187
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
188
|
+
**Options:**
|
|
189
|
+
- `captureArgs?: boolean` - Capture function arguments (default: true)
|
|
190
|
+
- `captureResult?: boolean` - Capture return value (default: true)
|
|
191
|
+
- `spanName?: string` - Custom span name (default: ClassName.methodName)
|
|
192
|
+
- `spanKind?: SpanKind` - OpenTelemetry span kind (default: INTERNAL)
|
|
235
193
|
|
|
236
|
-
###
|
|
194
|
+
### Utility Functions
|
|
195
|
+
|
|
196
|
+
- `getTracer(): Tracer` - Get the library's tracer instance
|
|
197
|
+
- `startSpan(name, options?, context?): Span` - Start a new span
|
|
198
|
+
- `withSpan<T>(name, fn, options?): Promise<T>` - Execute function within a span
|
|
199
|
+
- `setSpanAttributes(span, attributes): void` - Set multiple span attributes
|
|
200
|
+
- `createChildSpan(parent, name, options?): Span` - Create child span
|
|
201
|
+
|
|
202
|
+
### Constants
|
|
203
|
+
|
|
204
|
+
- `SpanKind` - OpenTelemetry span kinds
|
|
205
|
+
- `SpanStatusCode` - OpenTelemetry span status codes
|
|
206
|
+
|
|
207
|
+
## Examples
|
|
237
208
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
-
|
|
241
|
-
|
|
209
|
+
### Console Output (Development)
|
|
210
|
+
```bash
|
|
211
|
+
OTEL_SERVICE_NAME=my-service-dev
|
|
212
|
+
OTEL_TRACES_EXPORTER=console
|
|
213
|
+
```
|
|
242
214
|
|
|
243
|
-
###
|
|
215
|
+
### Jaeger (Local)
|
|
216
|
+
```bash
|
|
217
|
+
OTEL_SERVICE_NAME=my-service
|
|
218
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
219
|
+
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
|
220
|
+
```
|
|
244
221
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
-
|
|
222
|
+
### Honeycomb (Production)
|
|
223
|
+
```bash
|
|
224
|
+
OTEL_SERVICE_NAME=my-service
|
|
225
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
226
|
+
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
227
|
+
```
|
package/otel/package.json
CHANGED
package/otel/src/index.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
// OpenTelemetry
|
|
2
|
-
//
|
|
1
|
+
// OpenTelemetry instrumentation library
|
|
2
|
+
// This library provides instrumentation only - the host application
|
|
3
|
+
// is responsible for initializing the OpenTelemetry SDK
|
|
3
4
|
|
|
4
5
|
// Framework integration exports (internal use only)
|
|
5
6
|
export { createTRPCTracing } from './trpc-middleware';
|
|
6
7
|
export { createProviderInstrumentation } from './provider-instrumentation';
|
|
7
8
|
|
|
8
9
|
// Decorator for tracing functions
|
|
9
|
-
export { traced
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export { traced } from './trace-decorator';
|
|
11
|
+
export type { TracedOptions } from './trace-decorator';
|
|
12
|
+
|
|
13
|
+
// Utility functions for manual instrumentation
|
|
14
|
+
export {
|
|
15
|
+
getTracer,
|
|
16
|
+
startSpan,
|
|
17
|
+
withSpan,
|
|
18
|
+
setSpanAttributes,
|
|
19
|
+
createChildSpan,
|
|
20
|
+
SpanKind,
|
|
21
|
+
SpanStatusCode
|
|
22
|
+
} from './tracer';
|
package/otel/src/metrics.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { metrics, Meter, Counter, Histogram, UpDownCounter } from '@opentelemetry/api';
|
|
2
|
-
import { isOtelInitialized } from './sdk';
|
|
3
2
|
|
|
4
3
|
const METER_NAME = '@reactionary/otel';
|
|
5
4
|
const METER_VERSION = '0.0.1';
|
|
@@ -8,8 +7,9 @@ let globalMeter: Meter | null = null;
|
|
|
8
7
|
|
|
9
8
|
export function getMeter(): Meter {
|
|
10
9
|
if (!globalMeter) {
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// Simply get the meter from the API
|
|
11
|
+
// If the SDK is not initialized by the host application,
|
|
12
|
+
// this will return a NoopMeter
|
|
13
13
|
globalMeter = metrics.getMeter(METER_NAME, METER_VERSION);
|
|
14
14
|
}
|
|
15
15
|
return globalMeter;
|
|
@@ -84,8 +84,8 @@ function safeSerialize(value: unknown, maxDepth = 3, currentDepth = 0): string {
|
|
|
84
84
|
/**
|
|
85
85
|
* TypeScript decorator for tracing function execution
|
|
86
86
|
* Automatically creates OpenTelemetry spans for decorated methods
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
* Uses Stage 2 (legacy) decorator syntax
|
|
88
|
+
*
|
|
89
89
|
* @example
|
|
90
90
|
* ```typescript
|
|
91
91
|
* class MyService {
|
|
@@ -101,7 +101,7 @@ function safeSerialize(value: unknown, maxDepth = 3, currentDepth = 0): string {
|
|
|
101
101
|
* }
|
|
102
102
|
* ```
|
|
103
103
|
*/
|
|
104
|
-
export function traced(options: TracedOptions = {}):
|
|
104
|
+
export function traced(options: TracedOptions = {}): MethodDecorator {
|
|
105
105
|
const {
|
|
106
106
|
captureArgs = false,
|
|
107
107
|
captureResult = false,
|
|
@@ -109,42 +109,22 @@ export function traced(options: TracedOptions = {}): any {
|
|
|
109
109
|
spanKind = SpanKind.INTERNAL
|
|
110
110
|
} = options;
|
|
111
111
|
|
|
112
|
-
// Stage 2 (legacy) decorator
|
|
113
112
|
return function (
|
|
114
113
|
target: any,
|
|
115
|
-
propertyKey
|
|
116
|
-
descriptor
|
|
117
|
-
):
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Handle Stage 2 decorator
|
|
133
|
-
if (descriptor && typeof descriptor.value === 'function') {
|
|
134
|
-
const originalMethod = descriptor.value;
|
|
135
|
-
const methodName = String(propertyKey);
|
|
136
|
-
|
|
137
|
-
descriptor.value = createTracedMethod(originalMethod, methodName, {
|
|
138
|
-
captureArgs,
|
|
139
|
-
captureResult,
|
|
140
|
-
spanName,
|
|
141
|
-
spanKind
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
return descriptor;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return target;
|
|
114
|
+
propertyKey: string | symbol,
|
|
115
|
+
descriptor: PropertyDescriptor
|
|
116
|
+
): PropertyDescriptor {
|
|
117
|
+
const originalMethod = descriptor.value;
|
|
118
|
+
const methodName = String(propertyKey);
|
|
119
|
+
|
|
120
|
+
descriptor.value = createTracedMethod(originalMethod, methodName, {
|
|
121
|
+
captureArgs,
|
|
122
|
+
captureResult,
|
|
123
|
+
spanName,
|
|
124
|
+
spanKind
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return descriptor;
|
|
148
128
|
};
|
|
149
129
|
}
|
|
150
130
|
|
|
@@ -159,81 +139,80 @@ function createTracedMethod(
|
|
|
159
139
|
}
|
|
160
140
|
): any {
|
|
161
141
|
const { captureArgs, captureResult, spanName, spanKind } = options;
|
|
162
|
-
|
|
163
|
-
function tracedMethod(this: any, ...args: any[]): any {
|
|
142
|
+
|
|
143
|
+
async function tracedMethod(this: any, ...args: any[]): Promise<any> {
|
|
164
144
|
const tracer = getTracer();
|
|
165
145
|
const className = this?.constructor?.name || 'Unknown';
|
|
166
146
|
const effectiveSpanName = spanName || `${className}.${methodName}`;
|
|
167
147
|
|
|
168
|
-
//
|
|
169
|
-
|
|
148
|
+
// Use startActiveSpan to ensure proper context propagation
|
|
149
|
+
return tracer.startActiveSpan(effectiveSpanName, {
|
|
170
150
|
kind: spanKind,
|
|
171
151
|
attributes: {
|
|
172
152
|
'function.name': methodName,
|
|
173
153
|
'function.class': className,
|
|
174
154
|
}
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
span.setAttribute('function.args.count', args.length);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Helper function to finalize span with result
|
|
190
|
-
const finalizeSpan = (result: unknown, isError = false) => {
|
|
191
|
-
if (!isError && captureResult && result !== undefined) {
|
|
192
|
-
try {
|
|
193
|
-
span.setAttribute('function.result', safeSerialize(result));
|
|
194
|
-
} catch {
|
|
195
|
-
span.setAttribute('function.result', '[Serialization error]');
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (isError) {
|
|
200
|
-
span.setStatus({
|
|
201
|
-
code: SpanStatusCode.ERROR,
|
|
202
|
-
message: result instanceof Error ? result.message : String(result)
|
|
155
|
+
}, async (span) => {
|
|
156
|
+
// Capture arguments if enabled
|
|
157
|
+
if (captureArgs && args.length > 0) {
|
|
158
|
+
args.forEach((arg, index) => {
|
|
159
|
+
try {
|
|
160
|
+
span.setAttribute(`function.args.${index}`, safeSerialize(arg));
|
|
161
|
+
} catch {
|
|
162
|
+
span.setAttribute(`function.args.${index}`, '[Serialization error]');
|
|
163
|
+
}
|
|
203
164
|
});
|
|
204
|
-
|
|
205
|
-
span.recordException(result);
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
165
|
+
span.setAttribute('function.args.count', args.length);
|
|
209
166
|
}
|
|
210
167
|
|
|
211
|
-
span
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
168
|
+
// Helper function to set span attributes and status
|
|
169
|
+
const setSpanResult = (result: unknown, isError = false) => {
|
|
170
|
+
if (!isError && captureResult && result !== undefined) {
|
|
171
|
+
try {
|
|
172
|
+
span.setAttribute('function.result', safeSerialize(result));
|
|
173
|
+
} catch {
|
|
174
|
+
span.setAttribute('function.result', '[Serialization error]');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (isError) {
|
|
179
|
+
span.setStatus({
|
|
180
|
+
code: SpanStatusCode.ERROR,
|
|
181
|
+
message: result instanceof Error ? result.message : String(result)
|
|
182
|
+
});
|
|
183
|
+
if (result instanceof Error) {
|
|
184
|
+
span.recordException(result);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
span.end();
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const result = originalMethod.apply(this, args);
|
|
195
|
+
|
|
196
|
+
// Handle async functions - await them to keep span open
|
|
197
|
+
if (result instanceof Promise) {
|
|
198
|
+
try {
|
|
199
|
+
const value = await result;
|
|
200
|
+
setSpanResult(value);
|
|
222
201
|
return value;
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
finalizeSpan(error, true);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
setSpanResult(error, true);
|
|
226
204
|
throw error;
|
|
227
|
-
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Handle sync functions
|
|
209
|
+
setSpanResult(result);
|
|
210
|
+
return result;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
setSpanResult(error, true);
|
|
213
|
+
throw error;
|
|
228
214
|
}
|
|
229
|
-
|
|
230
|
-
// Handle sync functions
|
|
231
|
-
finalizeSpan(result);
|
|
232
|
-
return result;
|
|
233
|
-
} catch (error) {
|
|
234
|
-
finalizeSpan(error, true);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
215
|
+
});
|
|
237
216
|
}
|
|
238
217
|
|
|
239
218
|
// Preserve the original function's name and properties
|
package/otel/src/tracer.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
SpanOptions,
|
|
9
9
|
Attributes,
|
|
10
10
|
} from '@opentelemetry/api';
|
|
11
|
-
import { isOtelInitialized } from './sdk';
|
|
12
11
|
|
|
13
12
|
const TRACER_NAME = '@reactionary/otel';
|
|
14
13
|
const TRACER_VERSION = '0.0.1';
|
|
@@ -17,8 +16,9 @@ let globalTracer: Tracer | null = null;
|
|
|
17
16
|
|
|
18
17
|
export function getTracer(): Tracer {
|
|
19
18
|
if (!globalTracer) {
|
|
20
|
-
//
|
|
21
|
-
|
|
19
|
+
// Simply get the tracer from the API
|
|
20
|
+
// If the SDK is not initialized by the host application,
|
|
21
|
+
// this will return a ProxyTracer that produces NonRecordingSpans
|
|
22
22
|
globalTracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
23
23
|
}
|
|
24
24
|
return globalTracer;
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@reactionary/core';
|
|
11
11
|
import z from 'zod';
|
|
12
12
|
import { FakeConfiguration } from '../schema/configuration.schema';
|
|
13
|
-
import { Faker, en, base } from '@faker-js/faker
|
|
13
|
+
import { Faker, en, base } from '@faker-js/faker';
|
|
14
14
|
|
|
15
15
|
export class FakeCartProvider<
|
|
16
16
|
T extends Cart = Cart
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SearchProvider,
|
|
3
|
-
SearchQueryByTerm,
|
|
4
3
|
SearchResult,
|
|
5
4
|
SearchResultFacet,
|
|
6
5
|
SearchResultProduct,
|
|
7
|
-
Session,
|
|
8
6
|
Cache as ReactionaryCache,
|
|
9
7
|
} from '@reactionary/core';
|
|
8
|
+
import type { SearchQueryByTerm, Session } from '@reactionary/core';
|
|
10
9
|
import z from 'zod';
|
|
11
10
|
import { FakeConfiguration } from '../schema/configuration.schema';
|
|
12
11
|
import { Faker, en, base } from '@faker-js/faker';
|
|
13
12
|
import { jitter } from '../utilities/jitter';
|
|
13
|
+
import { traced } from '@reactionary/otel';
|
|
14
14
|
|
|
15
15
|
export class FakeSearchProvider<
|
|
16
16
|
T extends SearchResult = SearchResult
|
|
@@ -23,6 +23,7 @@ export class FakeSearchProvider<
|
|
|
23
23
|
this.config = config;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
@traced()
|
|
26
27
|
public override async queryByTerm(
|
|
27
28
|
payload: SearchQueryByTerm,
|
|
28
29
|
_session: Session
|
|
@@ -119,6 +120,14 @@ export class FakeSearchProvider<
|
|
|
119
120
|
},
|
|
120
121
|
} satisfies SearchResult;
|
|
121
122
|
|
|
123
|
+
const foo = this.childFunction();
|
|
124
|
+
|
|
122
125
|
return this.schema.parse(result);
|
|
123
126
|
}
|
|
127
|
+
|
|
128
|
+
@traced()
|
|
129
|
+
protected childFunction() {
|
|
130
|
+
const foo = 42;
|
|
131
|
+
return foo;
|
|
132
|
+
}
|
|
124
133
|
}
|
package/trpc/package.json
CHANGED
package/trpc/src/client.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { Client, Session
|
|
1
|
+
import { Client, Session } from '@reactionary/core';
|
|
2
2
|
import type { TransparentClient } from './types';
|
|
3
|
-
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
|
|
4
|
-
import type { TRPCClientError } from '@trpc/client';
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Configuration options for TRPC client creation
|
package/tsconfig.base.json
CHANGED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Metadata keys for TRPC method decorators
|
|
5
|
-
*/
|
|
6
|
-
export const TRPC_QUERY_METADATA_KEY = Symbol('trpc:query');
|
|
7
|
-
export const TRPC_MUTATION_METADATA_KEY = Symbol('trpc:mutation');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Options for TRPC method decorators
|
|
13
|
-
*/
|
|
14
|
-
export interface TRPCMethodOptions {
|
|
15
|
-
/** Custom name for the TRPC procedure (defaults to method name) */
|
|
16
|
-
name?: string;
|
|
17
|
-
/** Description for documentation purposes */
|
|
18
|
-
description?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Decorator to mark a provider method as a TRPC query procedure
|
|
23
|
-
* Query procedures are read-only operations (GET-like)
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* class ProductProvider extends BaseProvider {
|
|
28
|
-
* @trpcQuery()
|
|
29
|
-
* async getById(payload: ProductQueryById, session: Session): Promise<Product> {
|
|
30
|
-
* // implementation
|
|
31
|
-
* }
|
|
32
|
-
*
|
|
33
|
-
* @trpcQuery({ name: 'findBySlug', description: 'Find product by URL slug' })
|
|
34
|
-
* async getBySlug(payload: ProductQueryBySlug, session: Session): Promise<Product> {
|
|
35
|
-
* // implementation
|
|
36
|
-
* }
|
|
37
|
-
* }
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export function trpcQuery(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
41
|
-
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
42
|
-
// Store metadata about this method being a TRPC query
|
|
43
|
-
const metadata = {
|
|
44
|
-
methodName: String(propertyKey),
|
|
45
|
-
isQuery: true,
|
|
46
|
-
isMutation: false,
|
|
47
|
-
options
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Store on the prototype so it's inherited
|
|
51
|
-
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, metadata, target, propertyKey);
|
|
52
|
-
|
|
53
|
-
// Also store a list of all TRPC methods on the class
|
|
54
|
-
const existingMethods = Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target.constructor) || [];
|
|
55
|
-
existingMethods.push({ propertyKey, metadata });
|
|
56
|
-
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, existingMethods, target.constructor);
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Decorator to mark a provider method as a TRPC mutation procedure
|
|
62
|
-
* Mutation procedures are write operations that modify state (POST/PUT/DELETE-like)
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```typescript
|
|
66
|
-
* class CartProvider extends BaseProvider {
|
|
67
|
-
* @trpcMutation()
|
|
68
|
-
* async add(payload: CartAddMutation, session: Session): Promise<Cart> {
|
|
69
|
-
* // implementation
|
|
70
|
-
* }
|
|
71
|
-
*
|
|
72
|
-
* @trpcMutation({ name: 'removeItem' })
|
|
73
|
-
* async remove(payload: CartRemoveMutation, session: Session): Promise<Cart> {
|
|
74
|
-
* // implementation
|
|
75
|
-
* }
|
|
76
|
-
* }
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export function trpcMutation(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
80
|
-
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
81
|
-
// Store metadata about this method being a TRPC mutation
|
|
82
|
-
const metadata = {
|
|
83
|
-
methodName: String(propertyKey),
|
|
84
|
-
isQuery: false,
|
|
85
|
-
isMutation: true,
|
|
86
|
-
options
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Store on the prototype so it's inherited
|
|
90
|
-
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, metadata, target, propertyKey);
|
|
91
|
-
|
|
92
|
-
// Also store a list of all TRPC methods on the class
|
|
93
|
-
const existingMethods = Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target.constructor) || [];
|
|
94
|
-
existingMethods.push({ propertyKey, metadata });
|
|
95
|
-
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, existingMethods, target.constructor);
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Get all TRPC query methods from a class or instance
|
|
101
|
-
*/
|
|
102
|
-
export function getTRPCQueryMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
103
|
-
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
104
|
-
return Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, constructor) || [];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Get all TRPC mutation methods from a class or instance
|
|
109
|
-
*/
|
|
110
|
-
export function getTRPCMutationMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
111
|
-
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
112
|
-
return Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, constructor) || [];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get all TRPC methods (both queries and mutations) from a class or instance
|
|
117
|
-
*/
|
|
118
|
-
export function getAllTRPCMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
119
|
-
return [
|
|
120
|
-
...getTRPCQueryMethods(target),
|
|
121
|
-
...getTRPCMutationMethods(target)
|
|
122
|
-
];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Check if a method is marked as a TRPC query
|
|
127
|
-
*/
|
|
128
|
-
export function isTRPCQuery(target: any, methodName: string | symbol): boolean {
|
|
129
|
-
return !!Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target, methodName);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if a method is marked as a TRPC mutation
|
|
134
|
-
*/
|
|
135
|
-
export function isTRPCMutation(target: any, methodName: string | symbol): boolean {
|
|
136
|
-
return !!Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target, methodName);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Check if a method is marked for TRPC exposure (query or mutation)
|
|
141
|
-
*/
|
|
142
|
-
export function isTRPCMethod(target: any, methodName: string | symbol): boolean {
|
|
143
|
-
return isTRPCQuery(target, methodName) || isTRPCMutation(target, methodName);
|
|
144
|
-
}
|
package/otel/src/sdk.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
}
|