@worknice/instrumentation 1.0.1
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/LICENSE +16 -0
- package/README.md +202 -0
- package/dist/correlation.d.ts +68 -0
- package/dist/correlation.js +96 -0
- package/dist/correlation.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation-node.d.ts +12 -0
- package/dist/instrumentation-node.js +77 -0
- package/dist/instrumentation-node.js.map +1 -0
- package/dist/instrumentation.d.ts +40 -0
- package/dist/instrumentation.js +13 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/serverAction.d.ts +30 -0
- package/dist/serverAction.js +13 -0
- package/dist/serverAction.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Copyright (c) 2024 Worknice
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
4
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
7
|
+
furnished to do so, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
10
|
+
portions of the Software.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# @worknice/instrumentation
|
|
2
|
+
|
|
3
|
+
Shared instrumentation and correlation ID management for Worknice applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Correlation ID Tracking**: Request tracing across async boundaries using Node.js AsyncLocalStorage
|
|
8
|
+
- 📊 **Next.js Instrumentation**: Error reporting with Axiom
|
|
9
|
+
- 🔄 **Cross-Service Tracing**: Track requests across services (Notebook, MYOB, Micropay, Basics)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
This package is part of the Worknice monorepo and is automatically available to all apps:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @worknice/instrumentation
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Getting Started
|
|
20
|
+
|
|
21
|
+
### App Instrumentation
|
|
22
|
+
|
|
23
|
+
For Next.js apps, create an `instrumentation.ts` file in the app root:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// apps/$app-name/instrumentation.ts
|
|
27
|
+
export { register, onRequestError } from "@worknice/instrumentation";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `register()` - Initialises instrumentation. Automatically called by Next.js when instrumentation is enabled.
|
|
31
|
+
- `onRequestError()` - Handles and logs request errors with correlation tracking. Automatically called by Next.js on errors.
|
|
32
|
+
|
|
33
|
+
### Correlation ID Usage
|
|
34
|
+
|
|
35
|
+
The package provides multiple ways to work with correlation IDs:
|
|
36
|
+
|
|
37
|
+
#### Getting the current correlation ID
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { getCorrelationId } from "@worknice/instrumentation";
|
|
41
|
+
|
|
42
|
+
const correlationId = getCorrelationId();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Running code with a specific correlation ID
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { runWithCorrelationId, getCorrelationId } from "@worknice/instrumentation";
|
|
49
|
+
|
|
50
|
+
await runWithCorrelationId("$correlationId", async () => {
|
|
51
|
+
// All async operations here will have access to the correlation ID
|
|
52
|
+
const id = getCorrelationId(); // Returns "$correlationId"
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Getting or generating a correlation ID
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { getOrGenerateCorrelationId } from "@worknice/instrumentation";
|
|
60
|
+
|
|
61
|
+
const { correlationId, isNew } = getOrGenerateCorrelationId();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Extracting from HTTP headers
|
|
65
|
+
|
|
66
|
+
Extract correlation ID from HTTP headers in order:
|
|
67
|
+
|
|
68
|
+
- `x-request-id`
|
|
69
|
+
- `x-correlation-id`
|
|
70
|
+
- `request-id`
|
|
71
|
+
- `correlation-id`
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { extractCorrelationIdFromHeaders } from "@worknice/instrumentation";
|
|
75
|
+
|
|
76
|
+
const correlationId = extractCorrelationIdFromHeaders(request.headers);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
### Environment Variables
|
|
82
|
+
|
|
83
|
+
The package supports the following environment variables:
|
|
84
|
+
|
|
85
|
+
- `AXIOM_DATASET`: Axiom dataset name for error logging
|
|
86
|
+
- `AXIOM_TOKEN`: Axiom API token for authentication
|
|
87
|
+
- `NODE_ENV`: Environment name (development, staging, production)
|
|
88
|
+
|
|
89
|
+
When Axiom credentials are not provided, the package falls back to console logging.
|
|
90
|
+
|
|
91
|
+
## Architecture
|
|
92
|
+
|
|
93
|
+
### AsyncLocalStorage
|
|
94
|
+
|
|
95
|
+
The package uses Node.js AsyncLocalStorage to maintain correlation context across async boundaries without explicitly passing IDs through function calls.
|
|
96
|
+
This allows correlation IDs to automatically flow through:
|
|
97
|
+
|
|
98
|
+
- GraphQL operations
|
|
99
|
+
- Database queries
|
|
100
|
+
- API calls
|
|
101
|
+
- Background jobs
|
|
102
|
+
- Nested async operations
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
### Building
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pnpm --filter=@worknice/instrumentation build
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Development Mode
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pnpm --filter=@worknice/instrumentation dev
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Testing & Linting
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pnpm --filter=@worknice/instrumentation test:lint
|
|
122
|
+
pnpm --filter=@worknice/instrumentation test:types
|
|
123
|
+
pnpm --filter=@worknice/instrumentation test:format
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Use Cases
|
|
127
|
+
|
|
128
|
+
### Cross-Service Request Tracing
|
|
129
|
+
|
|
130
|
+
When a request flows through multiple services (e.g., Notebook → MYOB → External API), the correlation ID helps trace the entire request chain:
|
|
131
|
+
|
|
132
|
+
**In Notebook app:**
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { getOrGenerateCorrelationId } from "@worknice/instrumentation";
|
|
136
|
+
|
|
137
|
+
const { correlationId } = getOrGenerateCorrelationId();
|
|
138
|
+
await fetch("https://myob-service/api/endpoint", {
|
|
139
|
+
headers: {
|
|
140
|
+
"x-correlation-id": correlationId,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**In MYOB service:**
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { extractCorrelationIdFromHeaders, runWithCorrelationId } from "@worknice/instrumentation";
|
|
149
|
+
|
|
150
|
+
const correlationId = extractCorrelationIdFromHeaders(req.headers);
|
|
151
|
+
runWithCorrelationId(correlationId, async () => {
|
|
152
|
+
// All operations here share the same correlation ID
|
|
153
|
+
await processRequest();
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Background Job Tracking
|
|
158
|
+
|
|
159
|
+
Track background jobs initiated from web requests:
|
|
160
|
+
|
|
161
|
+
**In API route:**
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { getCorrelationId } from "@worknice/instrumentation";
|
|
165
|
+
|
|
166
|
+
const correlationId = getCorrelationId();
|
|
167
|
+
await inngest.send({
|
|
168
|
+
name: "process.data",
|
|
169
|
+
data: { correlationId, ...payload },
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**In Inngest function:**
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { runWithCorrelationId } from "@worknice/instrumentation";
|
|
177
|
+
|
|
178
|
+
export const processData = inngest.createFunction(
|
|
179
|
+
{ id: "process-data" },
|
|
180
|
+
{ event: "process.data" },
|
|
181
|
+
async ({ event }) => {
|
|
182
|
+
return runWithCorrelationId(event.data.correlationId, async () => {
|
|
183
|
+
// Job execution with correlation tracking
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Troubleshooting
|
|
190
|
+
|
|
191
|
+
### Correlation ID Not Available
|
|
192
|
+
|
|
193
|
+
If `getCorrelationId()` returns undefined:
|
|
194
|
+
|
|
195
|
+
1. Ensure you're within a correlation context (use `runWithCorrelationId`)
|
|
196
|
+
2. Verify the context hasn't been lost (e.g., through `setTimeout` without binding)
|
|
197
|
+
|
|
198
|
+
### Axiom Logs Not Appearing
|
|
199
|
+
|
|
200
|
+
1. Verify `AXIOM_DATASET` and `AXIOM_TOKEN` environment variables are set
|
|
201
|
+
2. Check console output on startup for "Axiom error logging enabled" message
|
|
202
|
+
3. Ensure the Axiom dataset exists and token has write permissions
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation ID management with lazy-loaded Node.js modules
|
|
3
|
+
* Node.js-specific imports are loaded inside functions to support Edge compilation
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Correlation context stored in AsyncLocalStorage
|
|
7
|
+
* Provides request-scoped correlation ID management
|
|
8
|
+
*/
|
|
9
|
+
interface CorrelationContext {
|
|
10
|
+
correlationId: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Initialise the AsyncLocalStorage instance
|
|
14
|
+
* Must be called once at app startup (e.g., in instrumentation.ts)
|
|
15
|
+
* Lazy loads the node:async_hooks module to avoid Edge compilation issues
|
|
16
|
+
*/
|
|
17
|
+
declare const initializeCorrelationStorage: () => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the current correlation ID from async context
|
|
20
|
+
* Returns undefined if not in a correlation context or if storage not initialised
|
|
21
|
+
* Note: initializeCorrelationStorage() must be called at app startup
|
|
22
|
+
*/
|
|
23
|
+
declare const getCorrelationId: () => string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Get the full correlation context
|
|
26
|
+
* Returns undefined if not in a correlation context or if storage not initialised
|
|
27
|
+
* Note: initializeCorrelationStorage() must be called at app startup
|
|
28
|
+
*/
|
|
29
|
+
declare const getCorrelationContext: () => CorrelationContext | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Generate a new correlation ID
|
|
32
|
+
* Ensures consistent ID generation across the application
|
|
33
|
+
* Edge-safe: uses crypto.randomUUID() which works in both runtimes
|
|
34
|
+
*
|
|
35
|
+
* @returns A new UUID v4 correlation ID
|
|
36
|
+
*/
|
|
37
|
+
declare const generateCorrelationId: () => string;
|
|
38
|
+
/**
|
|
39
|
+
* Run a function with a specific correlation ID
|
|
40
|
+
* Creates a new async context with the provided correlation ID
|
|
41
|
+
* Ensures storage is initialised before running
|
|
42
|
+
*/
|
|
43
|
+
declare function runWithCorrelationId<T>(correlationId: string, fn: () => T | Promise<T>): Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Get or generate a correlation ID
|
|
46
|
+
* Checks async context first, then generates a new UUID if needed
|
|
47
|
+
* Note: This does not set the correlation ID in async context.
|
|
48
|
+
* Use runWithCorrelationId to establish context.
|
|
49
|
+
*/
|
|
50
|
+
declare const getOrGenerateCorrelationId: () => {
|
|
51
|
+
correlationId: string;
|
|
52
|
+
isNew: boolean;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Check if we're currently in a correlation context
|
|
56
|
+
*/
|
|
57
|
+
declare const hasCorrelationContext: () => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Extract correlation ID from HTTP headers
|
|
60
|
+
* Supports both Node.js IncomingHttpHeaders and Fetch API Headers
|
|
61
|
+
* Edge-safe: only uses header parsing, no Node.js-specific APIs
|
|
62
|
+
*
|
|
63
|
+
* @param headers - Either Node.js headers object or Fetch API Headers
|
|
64
|
+
* @returns The correlation ID if found, undefined otherwise
|
|
65
|
+
*/
|
|
66
|
+
declare const extractCorrelationIdFromHeaders: (headers: Headers | Record<string, string | string[] | undefined>) => string | undefined;
|
|
67
|
+
|
|
68
|
+
export { type CorrelationContext, extractCorrelationIdFromHeaders, generateCorrelationId, getCorrelationContext, getCorrelationId, getOrGenerateCorrelationId, hasCorrelationContext, initializeCorrelationStorage, runWithCorrelationId };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
let correlationStorage;
|
|
2
|
+
let initializationPromise;
|
|
3
|
+
const initializeCorrelationStorage = async () => {
|
|
4
|
+
if (correlationStorage) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (initializationPromise) {
|
|
8
|
+
await initializationPromise;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
initializationPromise = (async () => {
|
|
12
|
+
try {
|
|
13
|
+
const { AsyncLocalStorage } = await import("node:async_hooks");
|
|
14
|
+
correlationStorage = new AsyncLocalStorage();
|
|
15
|
+
return correlationStorage;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.warn("Failed to initialize AsyncLocalStorage (expected in Edge runtime):", error);
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
21
|
+
await initializationPromise;
|
|
22
|
+
};
|
|
23
|
+
const getCorrelationId = () => {
|
|
24
|
+
if (!correlationStorage) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
return correlationStorage.getStore()?.correlationId;
|
|
28
|
+
};
|
|
29
|
+
const getCorrelationContext = () => {
|
|
30
|
+
if (!correlationStorage) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
return correlationStorage.getStore();
|
|
34
|
+
};
|
|
35
|
+
const generateCorrelationId = () => {
|
|
36
|
+
return crypto.randomUUID();
|
|
37
|
+
};
|
|
38
|
+
async function runWithCorrelationId(correlationId, fn) {
|
|
39
|
+
const context = {
|
|
40
|
+
correlationId
|
|
41
|
+
};
|
|
42
|
+
await initializeCorrelationStorage();
|
|
43
|
+
if (!correlationStorage) {
|
|
44
|
+
console.warn("AsyncLocalStorage not available, executing without context");
|
|
45
|
+
return fn();
|
|
46
|
+
}
|
|
47
|
+
return correlationStorage.run(context, fn);
|
|
48
|
+
}
|
|
49
|
+
const getOrGenerateCorrelationId = () => {
|
|
50
|
+
const existing = getCorrelationId();
|
|
51
|
+
if (existing) {
|
|
52
|
+
return { correlationId: existing, isNew: false };
|
|
53
|
+
}
|
|
54
|
+
return { correlationId: crypto.randomUUID(), isNew: true };
|
|
55
|
+
};
|
|
56
|
+
const hasCorrelationContext = () => {
|
|
57
|
+
return getCorrelationContext() !== void 0;
|
|
58
|
+
};
|
|
59
|
+
const CORRELATION_HEADERS = [
|
|
60
|
+
"x-request-id",
|
|
61
|
+
"x-correlation-id",
|
|
62
|
+
"request-id",
|
|
63
|
+
"correlation-id"
|
|
64
|
+
];
|
|
65
|
+
const extractCorrelationIdFromHeaders = (headers) => {
|
|
66
|
+
if (headers instanceof Headers) {
|
|
67
|
+
for (const headerName of CORRELATION_HEADERS) {
|
|
68
|
+
const value = headers.get(headerName);
|
|
69
|
+
if (value) return value;
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
for (const headerName of CORRELATION_HEADERS) {
|
|
74
|
+
const value = headers[headerName] || headers[headerName.toLowerCase()];
|
|
75
|
+
if (value) {
|
|
76
|
+
return Array.isArray(value) ? value[0] : value;
|
|
77
|
+
}
|
|
78
|
+
const upperHeaderName = headerName.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("-");
|
|
79
|
+
const upperValue = headers[upperHeaderName];
|
|
80
|
+
if (upperValue) {
|
|
81
|
+
return Array.isArray(upperValue) ? upperValue[0] : upperValue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return void 0;
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
extractCorrelationIdFromHeaders,
|
|
88
|
+
generateCorrelationId,
|
|
89
|
+
getCorrelationContext,
|
|
90
|
+
getCorrelationId,
|
|
91
|
+
getOrGenerateCorrelationId,
|
|
92
|
+
hasCorrelationContext,
|
|
93
|
+
initializeCorrelationStorage,
|
|
94
|
+
runWithCorrelationId
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=correlation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * Correlation ID management with lazy-loaded Node.js modules\n * Node.js-specific imports are loaded inside functions to support Edge compilation\n */\n\n/**\n * Correlation context stored in AsyncLocalStorage\n * Provides request-scoped correlation ID management\n */\nexport interface CorrelationContext {\n correlationId: string;\n}\n\n/**\n * Minimal type definition for AsyncLocalStorage methods used\n * Avoids module-level imports while maintaining type safety\n */\ninterface AsyncLocalStorageType {\n getStore(): CorrelationContext | undefined;\n run<T>(store: CorrelationContext, fn: () => T | Promise<T>): T | Promise<T>;\n}\n\n/**\n * Lazy-loaded AsyncLocalStorage instance\n * Only initialised when first accessed in Node.js runtime\n */\nlet correlationStorage: AsyncLocalStorageType | undefined;\n\n/**\n * Promise to ensure single initialisation of AsyncLocalStorage\n */\nlet initializationPromise: Promise<AsyncLocalStorageType | undefined> | undefined;\n\n/**\n * Initialise the AsyncLocalStorage instance\n * Must be called once at app startup (e.g., in instrumentation.ts)\n * Lazy loads the node:async_hooks module to avoid Edge compilation issues\n */\nexport const initializeCorrelationStorage = async (): Promise<void> => {\n if (correlationStorage) {\n return;\n }\n\n if (initializationPromise) {\n await initializationPromise;\n return;\n }\n\n initializationPromise = (async () => {\n try {\n const { AsyncLocalStorage } = await import(\"node:async_hooks\");\n correlationStorage = new AsyncLocalStorage<CorrelationContext>();\n return correlationStorage;\n } catch (error) {\n console.warn(\"Failed to initialize AsyncLocalStorage (expected in Edge runtime):\", error);\n return undefined;\n }\n })();\n\n await initializationPromise;\n};\n\n/**\n * Get the current correlation ID from async context\n * Returns undefined if not in a correlation context or if storage not initialised\n * Note: initializeCorrelationStorage() must be called at app startup\n */\nexport const getCorrelationId = (): string | undefined => {\n if (!correlationStorage) {\n return undefined;\n }\n return correlationStorage.getStore()?.correlationId;\n};\n\n/**\n * Get the full correlation context\n * Returns undefined if not in a correlation context or if storage not initialised\n * Note: initializeCorrelationStorage() must be called at app startup\n */\nexport const getCorrelationContext = (): CorrelationContext | undefined => {\n if (!correlationStorage) {\n return undefined;\n }\n return correlationStorage.getStore();\n};\n\n/**\n * Generate a new correlation ID\n * Ensures consistent ID generation across the application\n * Edge-safe: uses crypto.randomUUID() which works in both runtimes\n *\n * @returns A new UUID v4 correlation ID\n */\nexport const generateCorrelationId = (): string => {\n return crypto.randomUUID();\n};\n\n/**\n * Run a function with a specific correlation ID\n * Creates a new async context with the provided correlation ID\n * Ensures storage is initialised before running\n */\nexport async function runWithCorrelationId<T>(\n correlationId: string,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const context: CorrelationContext = {\n correlationId,\n };\n\n await initializeCorrelationStorage();\n\n if (!correlationStorage) {\n console.warn(\"AsyncLocalStorage not available, executing without context\");\n return fn();\n }\n\n return correlationStorage.run(context, fn);\n}\n\n/**\n * Get or generate a correlation ID\n * Checks async context first, then generates a new UUID if needed\n * Note: This does not set the correlation ID in async context.\n * Use runWithCorrelationId to establish context.\n */\nexport const getOrGenerateCorrelationId = (): {\n correlationId: string;\n isNew: boolean;\n} => {\n const existing = getCorrelationId();\n if (existing) {\n return { correlationId: existing, isNew: false };\n }\n\n return { correlationId: crypto.randomUUID(), isNew: true };\n};\n\n/**\n * Check if we're currently in a correlation context\n */\nexport const hasCorrelationContext = (): boolean => {\n return getCorrelationContext() !== undefined;\n};\n\n/**\n * Standard header names for correlation IDs (in order of preference)\n * Note: Fetch API Headers.get() is case-insensitive, but Node.js headers are case-sensitive\n */\nconst CORRELATION_HEADERS = [\n \"x-request-id\",\n \"x-correlation-id\",\n \"request-id\",\n \"correlation-id\",\n] as const;\n\n/**\n * Extract correlation ID from HTTP headers\n * Supports both Node.js IncomingHttpHeaders and Fetch API Headers\n * Edge-safe: only uses header parsing, no Node.js-specific APIs\n *\n * @param headers - Either Node.js headers object or Fetch API Headers\n * @returns The correlation ID if found, undefined otherwise\n */\nexport const extractCorrelationIdFromHeaders = (\n headers: Headers | Record<string, string | string[] | undefined>,\n): string | undefined => {\n if (headers instanceof Headers) {\n for (const headerName of CORRELATION_HEADERS) {\n const value = headers.get(headerName);\n if (value) return value;\n }\n return undefined;\n }\n\n for (const headerName of CORRELATION_HEADERS) {\n const value = headers[headerName] || headers[headerName.toLowerCase()];\n if (value) {\n // Handle array values (Node.js can return arrays for headers)\n return Array.isArray(value) ? value[0] : value;\n }\n\n const upperHeaderName = headerName\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"-\");\n\n const upperValue = headers[upperHeaderName];\n if (upperValue) {\n return Array.isArray(upperValue) ? upperValue[0] : upperValue;\n }\n }\n\n return undefined;\n};\n"],"mappings":"AA0BA,IAAI;AAKJ,IAAI;AAOG,MAAM,+BAA+B,YAA2B;AACrE,MAAI,oBAAoB;AACtB;AAAA,EACF;AAEA,MAAI,uBAAuB;AACzB,UAAM;AACN;AAAA,EACF;AAEA,2BAAyB,YAAY;AACnC,QAAI;AACF,YAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,kBAAkB;AAC7D,2BAAqB,IAAI,kBAAsC;AAC/D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,KAAK,sEAAsE,KAAK;AACxF,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM;AACR;AAOO,MAAM,mBAAmB,MAA0B;AACxD,MAAI,CAAC,oBAAoB;AACvB,WAAO;AAAA,EACT;AACA,SAAO,mBAAmB,SAAS,GAAG;AACxC;AAOO,MAAM,wBAAwB,MAAsC;AACzE,MAAI,CAAC,oBAAoB;AACvB,WAAO;AAAA,EACT;AACA,SAAO,mBAAmB,SAAS;AACrC;AASO,MAAM,wBAAwB,MAAc;AACjD,SAAO,OAAO,WAAW;AAC3B;AAOA,eAAsB,qBACpB,eACA,IACY;AACZ,QAAM,UAA8B;AAAA,IAClC;AAAA,EACF;AAEA,QAAM,6BAA6B;AAEnC,MAAI,CAAC,oBAAoB;AACvB,YAAQ,KAAK,4DAA4D;AACzE,WAAO,GAAG;AAAA,EACZ;AAEA,SAAO,mBAAmB,IAAI,SAAS,EAAE;AAC3C;AAQO,MAAM,6BAA6B,MAGrC;AACH,QAAM,WAAW,iBAAiB;AAClC,MAAI,UAAU;AACZ,WAAO,EAAE,eAAe,UAAU,OAAO,MAAM;AAAA,EACjD;AAEA,SAAO,EAAE,eAAe,OAAO,WAAW,GAAG,OAAO,KAAK;AAC3D;AAKO,MAAM,wBAAwB,MAAe;AAClD,SAAO,sBAAsB,MAAM;AACrC;AAMA,MAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,MAAM,kCAAkC,CAC7C,YACuB;AACvB,MAAI,mBAAmB,SAAS;AAC9B,eAAW,cAAc,qBAAqB;AAC5C,YAAM,QAAQ,QAAQ,IAAI,UAAU;AACpC,UAAI,MAAO,QAAO;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AAEA,aAAW,cAAc,qBAAqB;AAC5C,UAAM,QAAQ,QAAQ,UAAU,KAAK,QAAQ,WAAW,YAAY,CAAC;AACrE,QAAI,OAAO;AAET,aAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAAA,IAC3C;AAEA,UAAM,kBAAkB,WACrB,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AAEX,UAAM,aAAa,QAAQ,eAAe;AAC1C,QAAI,YAAY;AACd,aAAO,MAAM,QAAQ,UAAU,IAAI,WAAW,CAAC,IAAI;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { extractCorrelationIdFromHeaders, generateCorrelationId, getCorrelationContext, getCorrelationId, getOrGenerateCorrelationId, hasCorrelationContext, runWithCorrelationId } from './correlation.js';
|
|
2
|
+
export { onRequestError, register } from './instrumentation.js';
|
|
3
|
+
export { default as withCorrelationId } from './serverAction.js';
|
|
4
|
+
import 'next';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractCorrelationIdFromHeaders,
|
|
3
|
+
generateCorrelationId,
|
|
4
|
+
getCorrelationContext,
|
|
5
|
+
getCorrelationId,
|
|
6
|
+
getOrGenerateCorrelationId,
|
|
7
|
+
hasCorrelationContext,
|
|
8
|
+
runWithCorrelationId
|
|
9
|
+
} from "./correlation.js";
|
|
10
|
+
import { onRequestError, register } from "./instrumentation.js";
|
|
11
|
+
import { withCorrelationId } from "./serverAction.js";
|
|
12
|
+
export {
|
|
13
|
+
extractCorrelationIdFromHeaders,
|
|
14
|
+
generateCorrelationId,
|
|
15
|
+
getCorrelationContext,
|
|
16
|
+
getCorrelationId,
|
|
17
|
+
getOrGenerateCorrelationId,
|
|
18
|
+
hasCorrelationContext,
|
|
19
|
+
onRequestError,
|
|
20
|
+
register,
|
|
21
|
+
runWithCorrelationId,
|
|
22
|
+
withCorrelationId
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @worknice/instrumentation\n *\n * Shared instrumentation and correlation ID management for Worknice applications\n */\n\nexport {\n extractCorrelationIdFromHeaders,\n generateCorrelationId,\n getCorrelationContext,\n getCorrelationId,\n getOrGenerateCorrelationId,\n hasCorrelationContext,\n runWithCorrelationId,\n} from \"./correlation.js\";\nexport { onRequestError, register } from \"./instrumentation.js\";\nexport { withCorrelationId } from \"./serverAction.js\";\n"],"mappings":"AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB,gBAAgB;AACzC,SAAS,yBAAyB;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Instrumentation } from 'next';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node.js-specific instrumentation
|
|
5
|
+
* This file is only loaded when running in Node.js runtime
|
|
6
|
+
* It has full access to Node.js APIs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare function register(): Promise<void>;
|
|
10
|
+
declare const onRequestError: Instrumentation.onRequestError;
|
|
11
|
+
|
|
12
|
+
export { onRequestError, register };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { WinstonTransport as AxiomTransport } from "@axiomhq/winston";
|
|
2
|
+
import winston from "winston";
|
|
3
|
+
import {
|
|
4
|
+
extractCorrelationIdFromHeaders,
|
|
5
|
+
getCorrelationId,
|
|
6
|
+
initializeCorrelationStorage
|
|
7
|
+
} from "./correlation.js";
|
|
8
|
+
const dynamicMetaFormat = winston.format((info) => {
|
|
9
|
+
const correlationId = getCorrelationId();
|
|
10
|
+
return correlationId ? { ...info, correlationId } : info;
|
|
11
|
+
});
|
|
12
|
+
const createErrorLogger = () => {
|
|
13
|
+
const axiomDataset = process.env.AXIOM_DATASET;
|
|
14
|
+
const axiomToken = process.env.AXIOM_TOKEN;
|
|
15
|
+
return winston.createLogger({
|
|
16
|
+
level: "error",
|
|
17
|
+
format: winston.format.combine(dynamicMetaFormat(), winston.format.json()),
|
|
18
|
+
defaultMeta: {
|
|
19
|
+
source: "instrumentation",
|
|
20
|
+
environment: process.env.NODE_ENV || "development",
|
|
21
|
+
runtime: "nodejs"
|
|
22
|
+
},
|
|
23
|
+
transports: [
|
|
24
|
+
axiomDataset && axiomToken ? new AxiomTransport({
|
|
25
|
+
dataset: axiomDataset,
|
|
26
|
+
token: axiomToken
|
|
27
|
+
}) : new winston.transports.Console()
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const errorLogger = createErrorLogger();
|
|
32
|
+
async function register() {
|
|
33
|
+
await initializeCorrelationStorage();
|
|
34
|
+
console.log("\n\n========================================");
|
|
35
|
+
console.log("[Instrumentation] Correlation tracking initialised (Node.js runtime)");
|
|
36
|
+
if (process.env.AXIOM_DATASET && process.env.AXIOM_TOKEN) {
|
|
37
|
+
console.log("[Instrumentation] Axiom error logging enabled");
|
|
38
|
+
} else {
|
|
39
|
+
console.log("[Instrumentation] Axiom not configured - using console for errors");
|
|
40
|
+
}
|
|
41
|
+
console.log("========================================\n\n");
|
|
42
|
+
}
|
|
43
|
+
const onRequestError = (error, request, context) => {
|
|
44
|
+
const typedError = error;
|
|
45
|
+
let correlationId = getCorrelationId();
|
|
46
|
+
if (!correlationId) {
|
|
47
|
+
correlationId = extractCorrelationIdFromHeaders(request.headers);
|
|
48
|
+
}
|
|
49
|
+
const errorData = {
|
|
50
|
+
correlationId,
|
|
51
|
+
digest: typedError.digest,
|
|
52
|
+
error: {
|
|
53
|
+
message: typedError.message,
|
|
54
|
+
stack: typedError.stack,
|
|
55
|
+
name: typedError.name
|
|
56
|
+
},
|
|
57
|
+
request: {
|
|
58
|
+
path: request.path,
|
|
59
|
+
method: request.method,
|
|
60
|
+
headers: request.headers
|
|
61
|
+
},
|
|
62
|
+
context: {
|
|
63
|
+
routerKind: context.routerKind,
|
|
64
|
+
routePath: context.routePath,
|
|
65
|
+
routeType: context.routeType,
|
|
66
|
+
renderSource: context.renderSource,
|
|
67
|
+
revalidateReason: context.revalidateReason
|
|
68
|
+
},
|
|
69
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
70
|
+
};
|
|
71
|
+
errorLogger.error("[Request Error]", errorData);
|
|
72
|
+
};
|
|
73
|
+
export {
|
|
74
|
+
onRequestError,
|
|
75
|
+
register
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=instrumentation-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/instrumentation-node.ts"],"sourcesContent":["/**\n * Node.js-specific instrumentation\n * This file is only loaded when running in Node.js runtime\n * It has full access to Node.js APIs\n */\n\nimport { WinstonTransport as AxiomTransport } from \"@axiomhq/winston\";\nimport type { Instrumentation } from \"next\";\nimport winston from \"winston\";\n\nimport {\n extractCorrelationIdFromHeaders,\n getCorrelationId,\n initializeCorrelationStorage,\n} from \"./correlation.js\";\n\nconst dynamicMetaFormat = winston.format((info) => {\n const correlationId = getCorrelationId();\n return correlationId ? { ...info, correlationId } : info;\n});\n\n/**\n * Create Axiom logger for error reporting\n * Falls back to console if Axiom credentials are not configured\n */\nconst createErrorLogger = () => {\n const axiomDataset = process.env.AXIOM_DATASET;\n const axiomToken = process.env.AXIOM_TOKEN;\n\n return winston.createLogger({\n level: \"error\",\n format: winston.format.combine(dynamicMetaFormat(), winston.format.json()),\n defaultMeta: {\n source: \"instrumentation\",\n environment: process.env.NODE_ENV || \"development\",\n runtime: \"nodejs\",\n },\n transports: [\n axiomDataset && axiomToken\n ? new AxiomTransport({\n dataset: axiomDataset,\n token: axiomToken,\n })\n : new winston.transports.Console(),\n ],\n });\n};\n\nconst errorLogger = createErrorLogger();\n\nexport async function register() {\n await initializeCorrelationStorage();\n\n console.log(\"\\n\\n========================================\");\n console.log(\"[Instrumentation] Correlation tracking initialised (Node.js runtime)\");\n if (process.env.AXIOM_DATASET && process.env.AXIOM_TOKEN) {\n console.log(\"[Instrumentation] Axiom error logging enabled\");\n } else {\n console.log(\"[Instrumentation] Axiom not configured - using console for errors\");\n }\n console.log(\"========================================\\n\\n\");\n}\n\nexport const onRequestError: Instrumentation.onRequestError = (error, request, context) => {\n const typedError = error as Error & { digest?: string };\n\n let correlationId = getCorrelationId();\n if (!correlationId) {\n correlationId = extractCorrelationIdFromHeaders(request.headers);\n }\n\n const errorData = {\n correlationId,\n digest: typedError.digest,\n error: {\n message: typedError.message,\n stack: typedError.stack,\n name: typedError.name,\n },\n request: {\n path: request.path,\n method: request.method,\n headers: request.headers,\n },\n context: {\n routerKind: context.routerKind,\n routePath: context.routePath,\n routeType: context.routeType,\n renderSource: context.renderSource,\n revalidateReason: context.revalidateReason,\n },\n timestamp: new Date().toISOString(),\n };\n\n errorLogger.error(\"[Request Error]\", errorData);\n};\n"],"mappings":"AAMA,SAAS,oBAAoB,sBAAsB;AAEnD,OAAO,aAAa;AAEpB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,oBAAoB,QAAQ,OAAO,CAAC,SAAS;AACjD,QAAM,gBAAgB,iBAAiB;AACvC,SAAO,gBAAgB,EAAE,GAAG,MAAM,cAAc,IAAI;AACtD,CAAC;AAMD,MAAM,oBAAoB,MAAM;AAC9B,QAAM,eAAe,QAAQ,IAAI;AACjC,QAAM,aAAa,QAAQ,IAAI;AAE/B,SAAO,QAAQ,aAAa;AAAA,IAC1B,OAAO;AAAA,IACP,QAAQ,QAAQ,OAAO,QAAQ,kBAAkB,GAAG,QAAQ,OAAO,KAAK,CAAC;AAAA,IACzE,aAAa;AAAA,MACX,QAAQ;AAAA,MACR,aAAa,QAAQ,IAAI,YAAY;AAAA,MACrC,SAAS;AAAA,IACX;AAAA,IACA,YAAY;AAAA,MACV,gBAAgB,aACZ,IAAI,eAAe;AAAA,QACjB,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC,IACD,IAAI,QAAQ,WAAW,QAAQ;AAAA,IACrC;AAAA,EACF,CAAC;AACH;AAEA,MAAM,cAAc,kBAAkB;AAEtC,eAAsB,WAAW;AAC/B,QAAM,6BAA6B;AAEnC,UAAQ,IAAI,8CAA8C;AAC1D,UAAQ,IAAI,sEAAsE;AAClF,MAAI,QAAQ,IAAI,iBAAiB,QAAQ,IAAI,aAAa;AACxD,YAAQ,IAAI,+CAA+C;AAAA,EAC7D,OAAO;AACL,YAAQ,IAAI,mEAAmE;AAAA,EACjF;AACA,UAAQ,IAAI,8CAA8C;AAC5D;AAEO,MAAM,iBAAiD,CAAC,OAAO,SAAS,YAAY;AACzF,QAAM,aAAa;AAEnB,MAAI,gBAAgB,iBAAiB;AACrC,MAAI,CAAC,eAAe;AAClB,oBAAgB,gCAAgC,QAAQ,OAAO;AAAA,EACjE;AAEA,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,QAAQ,WAAW;AAAA,IACnB,OAAO;AAAA,MACL,SAAS,WAAW;AAAA,MACpB,OAAO,WAAW;AAAA,MAClB,MAAM,WAAW;AAAA,IACnB;AAAA,IACA,SAAS;AAAA,MACP,MAAM,QAAQ;AAAA,MACd,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,IACnB;AAAA,IACA,SAAS;AAAA,MACP,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,MACnB,cAAc,QAAQ;AAAA,MACtB,kBAAkB,QAAQ;AAAA,IAC5B;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,cAAY,MAAM,mBAAmB,SAAS;AAChD;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Instrumentation } from 'next';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Next.js Instrumentation Entry Point
|
|
5
|
+
*
|
|
6
|
+
* This file serves as an Edge-safe entry point for instrumentation across all Next.js apps
|
|
7
|
+
* in the monorepo. It uses dynamic imports to prevent Node.js-specific modules from breaking
|
|
8
|
+
* Edge runtime compilation, even though our apps use Node.js runtime exclusively.
|
|
9
|
+
*
|
|
10
|
+
* Architecture:
|
|
11
|
+
* - instrumentation.ts (this file): Edge-safe entry point with dynamic imports
|
|
12
|
+
* - instrumentation-node.ts: Node.js implementation with Winston/Axiom logging
|
|
13
|
+
*
|
|
14
|
+
* The separation is necessary because Next.js compiles instrumentation for both runtimes
|
|
15
|
+
* regardless of the runtime configuration, and Winston/Axiom imports would cause Edge
|
|
16
|
+
* compilation errors if placed directly in this file.
|
|
17
|
+
*
|
|
18
|
+
* See https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation for more details.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize instrumentation for the application
|
|
23
|
+
*
|
|
24
|
+
* Dynamically loads the Node.js implementation which:
|
|
25
|
+
* - Initializes AsyncLocalStorage for correlation tracking
|
|
26
|
+
* - Sets up Winston logging with Axiom transport
|
|
27
|
+
* - Configures error reporting with correlation context
|
|
28
|
+
*/
|
|
29
|
+
declare function register(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Handle request errors with correlation context
|
|
32
|
+
*
|
|
33
|
+
* Dynamically loads the Node.js error handler which:
|
|
34
|
+
* - Extracts or generates correlation ID
|
|
35
|
+
* - Logs errors to Axiom with full context
|
|
36
|
+
* - Maintains correlation ID for Winston logging
|
|
37
|
+
*/
|
|
38
|
+
declare const onRequestError: Instrumentation.onRequestError;
|
|
39
|
+
|
|
40
|
+
export { onRequestError, register };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async function register() {
|
|
2
|
+
const { register: registerNode } = await import("./instrumentation-node.js");
|
|
3
|
+
return registerNode();
|
|
4
|
+
}
|
|
5
|
+
const onRequestError = async (error, request, context) => {
|
|
6
|
+
const { onRequestError: nodeHandler } = await import("./instrumentation-node.js");
|
|
7
|
+
return nodeHandler(error, request, context);
|
|
8
|
+
};
|
|
9
|
+
export {
|
|
10
|
+
onRequestError,
|
|
11
|
+
register
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=instrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/instrumentation.ts"],"sourcesContent":["/**\n * Next.js Instrumentation Entry Point\n *\n * This file serves as an Edge-safe entry point for instrumentation across all Next.js apps\n * in the monorepo. It uses dynamic imports to prevent Node.js-specific modules from breaking\n * Edge runtime compilation, even though our apps use Node.js runtime exclusively.\n *\n * Architecture:\n * - instrumentation.ts (this file): Edge-safe entry point with dynamic imports\n * - instrumentation-node.ts: Node.js implementation with Winston/Axiom logging\n *\n * The separation is necessary because Next.js compiles instrumentation for both runtimes\n * regardless of the runtime configuration, and Winston/Axiom imports would cause Edge\n * compilation errors if placed directly in this file.\n *\n * See https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation for more details.\n */\n\nimport type { Instrumentation } from \"next\";\n\n/**\n * Initialize instrumentation for the application\n *\n * Dynamically loads the Node.js implementation which:\n * - Initializes AsyncLocalStorage for correlation tracking\n * - Sets up Winston logging with Axiom transport\n * - Configures error reporting with correlation context\n */\nexport async function register() {\n const { register: registerNode } = await import(\"./instrumentation-node.js\");\n return registerNode();\n}\n\n/**\n * Handle request errors with correlation context\n *\n * Dynamically loads the Node.js error handler which:\n * - Extracts or generates correlation ID\n * - Logs errors to Axiom with full context\n * - Maintains correlation ID for Winston logging\n */\nexport const onRequestError: Instrumentation.onRequestError = async (error, request, context) => {\n const { onRequestError: nodeHandler } = await import(\"./instrumentation-node.js\");\n return nodeHandler(error, request, context);\n};\n"],"mappings":"AA4BA,eAAsB,WAAW;AAC/B,QAAM,EAAE,UAAU,aAAa,IAAI,MAAM,OAAO,2BAA2B;AAC3E,SAAO,aAAa;AACtB;AAUO,MAAM,iBAAiD,OAAO,OAAO,SAAS,YAAY;AAC/F,QAAM,EAAE,gBAAgB,YAAY,IAAI,MAAM,OAAO,2BAA2B;AAChF,SAAO,YAAY,OAAO,SAAS,OAAO;AAC5C;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Higher-order function for Next.js server actions with automatic correlation ID handling.
|
|
3
|
+
*
|
|
4
|
+
* This wrapper automatically establishes correlation context for server actions,
|
|
5
|
+
* eliminating the need for boilerplate correlation ID code in each action.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Using withCorrelationId
|
|
10
|
+
* const myAction = async (data: Data) => {
|
|
11
|
+
* // ... action logic (correlation context automatically available via getCorrelationId())
|
|
12
|
+
* };
|
|
13
|
+
* export default withCorrelationId(myAction);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a server action function with automatic correlation ID context.
|
|
18
|
+
*
|
|
19
|
+
* The returned function:
|
|
20
|
+
* 1. Gets or generates a correlation ID
|
|
21
|
+
* 2. Establishes correlation context using runWithCorrelationId
|
|
22
|
+
* 3. Executes the original handler within that context
|
|
23
|
+
* 4. Returns the handler's result
|
|
24
|
+
*
|
|
25
|
+
* @param handler - The server action function to wrap
|
|
26
|
+
* @returns A wrapped version with automatic correlation context
|
|
27
|
+
*/
|
|
28
|
+
declare function withCorrelationId<T extends any[], R>(handler: (...args: T) => Promise<R>): (...args: T) => Promise<R>;
|
|
29
|
+
|
|
30
|
+
export { withCorrelationId as default, withCorrelationId };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getOrGenerateCorrelationId, runWithCorrelationId } from "./correlation.js";
|
|
2
|
+
function withCorrelationId(handler) {
|
|
3
|
+
return async (...args) => {
|
|
4
|
+
const { correlationId } = getOrGenerateCorrelationId();
|
|
5
|
+
return runWithCorrelationId(correlationId, () => handler(...args));
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
var serverAction_default = withCorrelationId;
|
|
9
|
+
export {
|
|
10
|
+
serverAction_default as default,
|
|
11
|
+
withCorrelationId
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=serverAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serverAction.ts"],"sourcesContent":["/**\n * Higher-order function for Next.js server actions with automatic correlation ID handling.\n *\n * This wrapper automatically establishes correlation context for server actions,\n * eliminating the need for boilerplate correlation ID code in each action.\n *\n * @example\n * ```typescript\n * // Using withCorrelationId\n * const myAction = async (data: Data) => {\n * // ... action logic (correlation context automatically available via getCorrelationId())\n * };\n * export default withCorrelationId(myAction);\n * ```\n */\n\nimport { getOrGenerateCorrelationId, runWithCorrelationId } from \"./correlation.js\";\n\n/**\n * Wraps a server action function with automatic correlation ID context.\n *\n * The returned function:\n * 1. Gets or generates a correlation ID\n * 2. Establishes correlation context using runWithCorrelationId\n * 3. Executes the original handler within that context\n * 4. Returns the handler's result\n *\n * @param handler - The server action function to wrap\n * @returns A wrapped version with automatic correlation context\n */\nexport function withCorrelationId<T extends any[], R>(\n handler: (...args: T) => Promise<R>,\n): (...args: T) => Promise<R> {\n return async (...args: T): Promise<R> => {\n const { correlationId } = getOrGenerateCorrelationId();\n return runWithCorrelationId(correlationId, () => handler(...args));\n };\n}\n\nexport default withCorrelationId;\n"],"mappings":"AAgBA,SAAS,4BAA4B,4BAA4B;AAc1D,SAAS,kBACd,SAC4B;AAC5B,SAAO,UAAU,SAAwB;AACvC,UAAM,EAAE,cAAc,IAAI,2BAA2B;AACrD,WAAO,qBAAqB,eAAe,MAAM,QAAQ,GAAG,IAAI,CAAC;AAAA,EACnE;AACF;AAEA,IAAO,uBAAQ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@worknice/instrumentation",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "Shared instrumentation management for Worknice applications",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./*": {
|
|
17
|
+
"import": "./dist/*.js",
|
|
18
|
+
"types": "./dist/*.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@axiomhq/winston": "^1.2.0",
|
|
30
|
+
"next": "~15.5.0",
|
|
31
|
+
"winston": "^3.15.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@anolilab/semantic-release-pnpm": "^1.1.10",
|
|
35
|
+
"@total-typescript/tsconfig": "^1.0.4",
|
|
36
|
+
"@types/node": "^20.10.5",
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
|
38
|
+
"@typescript-eslint/parser": "^8.7.0",
|
|
39
|
+
"eslint": "^8.56.0",
|
|
40
|
+
"prettier": "^3.1.1",
|
|
41
|
+
"semantic-release": "^24.2.2",
|
|
42
|
+
"semantic-release-scope-filter": "^1.0.0",
|
|
43
|
+
"tsup": "^8.0.1",
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"vitest": "^3.1.1"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "rm -rf ./dist && tsup",
|
|
49
|
+
"dev": "rm -rf ./dist && tsup --watch",
|
|
50
|
+
"fix": "exit 0",
|
|
51
|
+
"fix:lint": "eslint \"src/**/*.{js,ts,tsx}\" --fix",
|
|
52
|
+
"fix:format": "prettier '**/*.{js,ts,tsx,css}' --write",
|
|
53
|
+
"test": "exit 0",
|
|
54
|
+
"test:unit": "vitest run",
|
|
55
|
+
"test:format": "prettier '**/*.{js,ts,tsx,css}' --check",
|
|
56
|
+
"test:lint": "eslint \"src/**/*.{js,ts,tsx}\"",
|
|
57
|
+
"test:types": "tsc --noEmit --pretty"
|
|
58
|
+
}
|
|
59
|
+
}
|