@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 CHANGED
@@ -3,6 +3,7 @@
3
3
  "version": "0.0.1",
4
4
  "dependencies": {
5
5
  "zod": "4.0.0-beta.20250430T185432",
6
- "@upstash/redis": "^1.34.9"
6
+ "@upstash/redis": "^1.34.9",
7
+ "@reactionary/otel": "0.0.1"
7
8
  }
8
9
  }
@@ -3,6 +3,7 @@ import { Session } from '../schemas/session.schema';
3
3
  import { BaseQuery } from '../schemas/queries/base.query';
4
4
  import { BaseMutation } from '../schemas/mutations/base.mutation';
5
5
  import { BaseModel } from '../schemas/models/base.model';
6
+ import { createProviderInstrumentation } from '@reactionary/otel';
6
7
 
7
8
  /**
8
9
  * Base capability provider, responsible for mutations (changes) and queries (fetches)
@@ -13,7 +14,11 @@ export abstract class BaseProvider<
13
14
  Q extends BaseQuery = BaseQuery,
14
15
  M extends BaseMutation = BaseMutation
15
16
  > {
16
- constructor(public readonly schema: z.ZodType<T>, public readonly querySchema: z.ZodType<Q, Q>, public readonly mutationSchema: z.ZodType<M, M>) {}
17
+ private instrumentation: ReturnType<typeof createProviderInstrumentation>;
18
+
19
+ constructor(public readonly schema: z.ZodType<T>, public readonly querySchema: z.ZodType<Q, Q>, public readonly mutationSchema: z.ZodType<M, M>) {
20
+ this.instrumentation = createProviderInstrumentation(this.constructor.name);
21
+ }
17
22
 
18
23
  /**
19
24
  * Validates that the final domain model constructed by the provider
@@ -37,13 +42,21 @@ export abstract class BaseProvider<
37
42
  * of the results will match the order of the queries.
38
43
  */
39
44
  public async query(queries: Q[], session: Session): Promise<T[]> {
40
- const results = await this.fetch(queries, session);
45
+ return this.instrumentation.traceQuery(
46
+ 'query',
47
+ async (span) => {
48
+ span.setAttribute('provider.query.count', queries.length);
49
+ const results = await this.fetch(queries, session);
41
50
 
42
- for (const result of results) {
43
- this.assert(result);
44
- }
51
+ for (const result of results) {
52
+ this.assert(result);
53
+ }
45
54
 
46
- return results;
55
+ span.setAttribute('provider.result.count', results.length);
56
+ return results;
57
+ },
58
+ { queryCount: queries.length }
59
+ );
47
60
  }
48
61
 
49
62
  /**
@@ -51,11 +64,18 @@ export abstract class BaseProvider<
51
64
  * resulting from that set of operations.
52
65
  */
53
66
  public async mutate(mutations: M[], session: Session): Promise<T> {
54
- const result = await this.process(mutations, session);
67
+ return this.instrumentation.traceMutation(
68
+ 'mutate',
69
+ async (span) => {
70
+ span.setAttribute('provider.mutation.count', mutations.length);
71
+ const result = await this.process(mutations, session);
55
72
 
56
- this.assert(result);
73
+ this.assert(result);
57
74
 
58
- return result;
75
+ return result;
76
+ },
77
+ { mutationCount: mutations.length }
78
+ );
59
79
  }
60
80
 
61
81
  /**
@@ -0,0 +1,52 @@
1
+ # OpenTelemetry Configuration (Standard Environment Variables)
2
+ # See: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
3
+ # ============================
4
+
5
+ # Service identification
6
+ OTEL_SERVICE_NAME=reactionary-trpc-example
7
+ OTEL_SERVICE_VERSION=1.0.0
8
+
9
+ # Traces exporter: console | otlp | otlp/http | none
10
+ OTEL_TRACES_EXPORTER=console
11
+
12
+ # Metrics exporter: console | otlp | otlp/http | none
13
+ OTEL_METRICS_EXPORTER=console
14
+
15
+ # For OTLP exporters (e.g., Honeycomb, Jaeger, Grafana)
16
+ # OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
17
+ # OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
18
+ # OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
19
+
20
+ # Or use specific endpoints for traces/metrics
21
+ # OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.honeycomb.io/v1/traces
22
+ # OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.honeycomb.io/v1/metrics
23
+
24
+ # For local Jaeger
25
+ # OTEL_TRACES_EXPORTER=otlp
26
+ # OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
27
+
28
+ # Debug logging
29
+ # OTEL_LOG_LEVEL=debug
30
+
31
+ # Metrics export interval (milliseconds)
32
+ OTEL_METRIC_EXPORT_INTERVAL=60000
33
+
34
+ # Session Store Configuration
35
+ # ============================
36
+ SESSION_STORE_REDIS_CONNECTION=redis://localhost:6379
37
+ SESSION_STORE_SECRET=your-session-secret-here
38
+
39
+ # Provider API Keys
40
+ # ==================
41
+ ALGOLIA_API_KEY=your-algolia-api-key
42
+ ALGOLIA_APP_ID=your-algolia-app-id
43
+ ALGOLIA_INDEX=your-algolia-index
44
+
45
+ COMMERCETOOLS_API_URL=https://api.commercetools.com
46
+ COMMERCETOOLS_AUTH_URL=https://auth.commercetools.com
47
+ COMMERCETOOLS_CLIENT_ID=your-client-id
48
+ COMMERCETOOLS_CLIENT_SECRET=your-client-secret
49
+ COMMERCETOOLS_PROJECT_KEY=your-project-key
50
+
51
+ POSTHOG_API_KEY=your-posthog-api-key
52
+ POSTHOG_HOST=https://app.posthog.com
@@ -52,4 +52,8 @@ app.use(
52
52
  })
53
53
  );
54
54
 
55
- app.listen(3000);
55
+ const server = app.listen(3000, () => {
56
+ console.log('Server started on http://localhost:3000');
57
+ // OpenTelemetry automatically initializes based on OTEL_* environment variables
58
+ });
59
+
package/otel/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # @reactionary/otel
2
+
3
+ Zero-configuration OpenTelemetry instrumentation for Reactionary framework. Automatically instruments tRPC routes and providers using standard OTEL environment variables.
4
+
5
+ ## Features
6
+
7
+ - **Zero Configuration**: Auto-initializes on first use with standard OTEL env vars
8
+ - **Automatic tRPC Route Tracing**: All tRPC procedures automatically traced
9
+ - **Provider Instrumentation**: BaseProvider operations automatically instrumented
10
+ - **Standard Compliance**: Uses official OpenTelemetry environment variables
11
+ - **Multiple Exporters**: Console, OTLP/HTTP, or custom exporters
12
+ - **Metrics Collection**: Request counts, durations, errors automatically tracked
13
+ - **Lazy Initialization**: Only starts when actually used
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @reactionary/otel
19
+ ```
20
+
21
+ That's it! No initialization code needed.
22
+
23
+ ## How It Works
24
+
25
+ The OTEL package automatically initializes itself on first use, reading configuration from standard OpenTelemetry environment variables. When your code first creates a span or metric, the SDK initializes with your environment configuration.
26
+
27
+ ```typescript
28
+ // No imports or initialization needed!
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
+ ```
67
+
68
+ See the [OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) for all available options.
69
+
70
+ ## Exporters
71
+
72
+ ### Console (Development)
73
+ ```bash
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
+ ```
87
+
88
+ ### Disable
89
+ ```bash
90
+ OTEL_TRACES_EXPORTER=none
91
+ OTEL_METRICS_EXPORTER=none
92
+ ```
93
+
94
+ ## Custom Instrumentation
95
+
96
+ ### Manual Spans
97
+
98
+ Create custom spans for specific operations:
99
+
100
+ ```typescript
101
+ import { withSpan, getTracer } from '@reactionary/otel';
102
+
103
+ // Using withSpan helper
104
+ const result = await withSpan('custom-operation', async (span) => {
105
+ span.setAttribute('custom.attribute', 'value');
106
+ // Your operation here
107
+ return someAsyncOperation();
108
+ });
109
+
110
+ // Using tracer directly
111
+ const tracer = getTracer();
112
+ const span = tracer.startSpan('manual-span');
113
+ try {
114
+ // Your operation
115
+ span.setStatus({ code: SpanStatusCode.OK });
116
+ } catch (error) {
117
+ span.recordException(error);
118
+ span.setStatus({ code: SpanStatusCode.ERROR });
119
+ throw error;
120
+ } finally {
121
+ span.end();
122
+ }
123
+ ```
124
+
125
+ ### Provider Instrumentation
126
+
127
+ Providers are automatically instrumented when extending BaseProvider:
128
+
129
+ ```typescript
130
+ import { BaseProvider } from '@reactionary/core';
131
+
132
+ class MyProvider extends BaseProvider {
133
+ // Automatically traced when OTEL is initialized
134
+ protected async fetch(queries, session) {
135
+ // Your implementation
136
+ }
137
+
138
+ protected async process(mutations, session) {
139
+ // Your implementation
140
+ }
141
+ }
142
+ ```
143
+
144
+ ### Custom Metrics
145
+
146
+ Track custom business metrics:
147
+
148
+ ```typescript
149
+ import { getMetrics } from '@reactionary/otel';
150
+
151
+ const metrics = getMetrics();
152
+
153
+ // Increment counter
154
+ metrics.requestCounter.add(1, {
155
+ 'endpoint': '/api/users',
156
+ 'method': 'GET'
157
+ });
158
+
159
+ // Record histogram
160
+ metrics.requestDuration.record(150, {
161
+ 'endpoint': '/api/users',
162
+ 'status': 'success'
163
+ });
164
+ ```
165
+
166
+ ## Examples
167
+
168
+ ### Honeycomb
169
+
170
+ ```bash
171
+ OTEL_SERVICE_NAME=my-service
172
+ OTEL_TRACES_EXPORTER=otlp
173
+ OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
174
+ OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
175
+ ```
176
+
177
+ ### Local Development
178
+
179
+ ```bash
180
+ OTEL_SERVICE_NAME=my-service-dev
181
+ OTEL_TRACES_EXPORTER=console
182
+ OTEL_METRICS_EXPORTER=none # Disable metrics in dev
183
+ ```
184
+
185
+ ### Docker Compose with Jaeger
186
+
187
+ ```yaml
188
+ services:
189
+ app:
190
+ environment:
191
+ - OTEL_EXPORTER_TYPE=otlp
192
+ - OTEL_COLLECTOR_ENDPOINT=http://jaeger:4318
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
200
+ ```
201
+
202
+ ## Metrics Reference
203
+
204
+ The following metrics are automatically collected:
205
+
206
+ | Metric | Type | Description |
207
+ |--------|------|-------------|
208
+ | `reactionary.requests` | Counter | Total number of requests |
209
+ | `reactionary.request.duration` | Histogram | Request duration in milliseconds |
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 |
216
+
217
+ ## Best Practices
218
+
219
+ 1. **Use Standard Variables**: Stick to OpenTelemetry standard environment variables
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
226
+
227
+ ## Troubleshooting
228
+
229
+ ### Traces Not Appearing
230
+
231
+ 1. Check OTEL is initialized before other components
232
+ 2. Verify `OTEL_TRACE_ENABLED` is not set to `false`
233
+ 3. Check exporter configuration and endpoint connectivity
234
+ 4. Look for initialization errors in console
235
+
236
+ ### Performance Impact
237
+
238
+ - Use sampling to reduce overhead
239
+ - Disable metrics if not needed
240
+ - Consider using batch exporters
241
+ - Increase export intervals for metrics
242
+
243
+ ### Memory Usage
244
+
245
+ - Monitor span processor queue size
246
+ - Adjust batch size and timeout
247
+ - Consider using sampling for high-volume services
@@ -0,0 +1,23 @@
1
+ import baseConfig from '../eslint.config.mjs';
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ files: ['**/*.json'],
7
+ rules: {
8
+ '@nx/dependency-checks': [
9
+ 'error',
10
+ {
11
+ ignoredFiles: [
12
+ '{projectRoot}/eslint.config.{js,cjs,mjs}',
13
+ '{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
14
+ '{projectRoot}/vite.config.{js,ts,mjs,mts}',
15
+ ],
16
+ },
17
+ ],
18
+ },
19
+ languageOptions: {
20
+ parser: await import('jsonc-eslint-parser'),
21
+ },
22
+ },
23
+ ];
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@reactionary/otel",
3
+ "version": "0.0.1",
4
+ "type": "commonjs",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "dependencies": {
8
+ "@opentelemetry/api": "^1.9.0",
9
+ "@opentelemetry/sdk-node": "^0.203.0",
10
+ "@trpc/server": "^11.1.2"
11
+ }
12
+ }